Compare commits
661 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
04b3fc0268 | ||
|
|
105ccc81a5 | ||
|
|
c349892c5b | ||
|
|
3d4b8ce99b | ||
|
|
3571307df5 | ||
|
|
64fd87134f | ||
|
|
82bba44538 | ||
|
|
df05f3a3c0 | ||
|
|
8e31ef7be6 | ||
|
|
f4555f7c96 | ||
|
|
8408da55ea | ||
|
|
4a67dd2e28 | ||
|
|
bd806a34d8 | ||
|
|
2f7e833a79 | ||
|
|
c17503ab78 | ||
|
|
19f76d34db | ||
|
|
25c8b2fabb | ||
|
|
bfd8bf9ca4 | ||
|
|
e6adbb0e42 | ||
|
|
11fc6be328 | ||
|
|
7a5405d2ab | ||
|
|
b9a24f00ad | ||
|
|
dbfc292353 | ||
|
|
a09255b2ff | ||
|
|
9d1c72cc07 | ||
|
|
09ac30ef2e | ||
|
|
0ec8f5d283 | ||
|
|
b872df0f31 | ||
|
|
0add4af208 | ||
|
|
298cd9e065 | ||
|
|
b715d20385 | ||
|
|
79e313a0c0 | ||
|
|
9f4c75d1c2 | ||
|
|
b37492644c | ||
|
|
04a792a8c2 | ||
|
|
35ec24c3f0 | ||
|
|
9a00ccdacc | ||
|
|
61b23677d1 | ||
|
|
90037dc6cd | ||
|
|
e8d48e1f43 | ||
|
|
a2f8c9c75b | ||
|
|
5fb83e7f52 | ||
|
|
dd6bf568d1 | ||
|
|
00ee164cef | ||
|
|
448071b02d | ||
|
|
4dad9d0e37 | ||
|
|
3a2117c5d3 | ||
|
|
26a3f6ddc3 | ||
|
|
7741312673 | ||
|
|
954a1723f1 | ||
|
|
87285d94f7 | ||
|
|
d260d0c182 | ||
|
|
137eb40fab | ||
|
|
af440460e1 | ||
|
|
a5c8472a37 | ||
|
|
dfae37421d | ||
|
|
438a8d8b75 | ||
|
|
ac2034561d | ||
|
|
c42cd4233d | ||
|
|
5b2a73e3eb | ||
|
|
6f163a6ba5 | ||
|
|
cc94cc7d01 | ||
|
|
94934ae2cf | ||
|
|
e361f88501 | ||
|
|
708c042b61 | ||
|
|
600599f49e | ||
|
|
1fe7bbbbc4 | ||
|
|
f83d7a7cd1 | ||
|
|
7094047b3d | ||
|
|
11d3607688 | ||
|
|
fcae58d355 | ||
|
|
ef9ace9e65 | ||
|
|
22e4039133 | ||
|
|
4971670e56 | ||
|
|
e16cab6b9c | ||
|
|
a643c3dba6 | ||
|
|
51abf5b0a6 | ||
|
|
1cee6e309b | ||
|
|
0777a660bf | ||
|
|
5e0479e414 | ||
|
|
942c77816d | ||
|
|
8e260d5c40 | ||
|
|
7951eee8a3 | ||
|
|
be2f7d7a8a | ||
|
|
59c8e8b46e | ||
|
|
98c8b28bf3 | ||
|
|
7bb5bc01b7 | ||
|
|
1482c07ae4 | ||
|
|
adba84ae6a | ||
|
|
146e296826 | ||
|
|
28c10dba09 | ||
|
|
e646a0840d | ||
|
|
74ea6bf00a | ||
|
|
d7ad784809 | ||
|
|
642bd10dcc | ||
|
|
7c566c36f4 | ||
|
|
dbbe038939 |
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
@@ -0,0 +1,2 @@
|
||||
bin/node_modules/*
|
||||
bin/templates/project/*
|
||||
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
@@ -0,0 +1,4 @@
|
||||
*.properties
|
||||
bin
|
||||
gen
|
||||
proguard-project.txt
|
||||
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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
|
||||
6
NOTICE
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
@@ -10,8 +10,6 @@ The Apache Software Foundation (http://www.apache.org)
|
||||
== in this case for the Android-specific code. ==
|
||||
=========================================================================
|
||||
|
||||
Android Code
|
||||
Copyright 2005-2008 The Android Open Source Project
|
||||
|
||||
This product includes software developed as part of
|
||||
The Android Open Source Project (http://source.android.com).
|
||||
|
||||
|
||||
65
README.md
Executable file → Normal file
@@ -7,9 +7,9 @@
|
||||
# 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
|
||||
@@ -18,74 +18,41 @@
|
||||
# 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.
|
||||
at the core, applications written with web technology: HTML, CSS and JavaScript.
|
||||
|
||||
[Apache Cordova](http://cordova.io) is a project at The Apache Software Foundation (ASF).
|
||||
[Apache Cordova](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
|
||||
---
|
||||
|
||||
The Cordova developer tooling is split between general tooling and project level tooling.
|
||||
## Cordova Android Developer Tools
|
||||
|
||||
General Commands
|
||||
We recommend using the [Cordova command-line tool](https://www.npmjs.com/package/cordova) to create projects and be able to easily install plugins.
|
||||
|
||||
./bin/create [path package activity] ... create the ./example app or a cordova android project
|
||||
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
|
||||
./cordova/build ........................ calls `clean` then compiles the project
|
||||
./cordova/log ........................ stream device or emulate logs to stdout
|
||||
./cordova/log ........................ streams device or emulator logs to STDOUT
|
||||
./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-18
|
||||
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/)
|
||||
|
||||
374
RELEASENOTES.md
@@ -7,9 +7,9 @@
|
||||
# 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
|
||||
@@ -19,6 +19,374 @@
|
||||
#
|
||||
-->
|
||||
## 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.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) ###
|
||||
|
||||
43 commits from 10 authors. Highlights include:
|
||||
|
||||
* Removing addJavascriptInterface support from all Android versions lower than 4.2 due to security vulnerability
|
||||
* CB-5917 Add a loadUrlIntoView overload that doesn't recreate plugins.
|
||||
* 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.
|
||||
* CB-5793 Don't clean before build and change output directory to ant-build to avoid conflicts with Eclipse.
|
||||
* CB-5803 Fix cordova/emulate on windows.
|
||||
* CB-5801 exec->spawn in build to make sure compile errors are shown.
|
||||
* CB-5799 Update version of OkHTTP to 1.3
|
||||
* CB-4910 Update CLI project template to point to config.xml at the root now that it's not in www/ by default.
|
||||
* CB-5504 Adding onDestroy to app plugin to deregister telephonyReceiver
|
||||
* CB-5715 Add Eclipse .project file to create template. For CLI projects, it adds refs for root www/ & config.xml and hides platform versions
|
||||
* CB-5447 Removed android:debuggable=“true” from project template.
|
||||
* CB-5714 Fix of android build when too big output stops build with error due to buffer overflow.
|
||||
* CB-5592 Set MIME type for openExternal when scheme is file:
|
||||
|
||||
### 3.3.0 (Dec 2013) ###
|
||||
|
||||
41 commits from 11 authors. Highlights include:
|
||||
|
||||
* CB-5481 Fix for Cordova trying to get config.xml from the wrong namespace
|
||||
* CB-5487 Enable Remote Debugging when your Android app is debuggable.
|
||||
* CB-5445 Adding onScrollChanged and the ScrollEvent object
|
||||
* CB-5422 Don't require JAVA_HOME to be defined
|
||||
* CB-5490 Add javadoc target to ant script
|
||||
* CB-5471 Deprecated DroidGap class
|
||||
* CB-5255 Prefer Google API targets over android-## targets when building.
|
||||
* CB-5232 Change create script to use Cordova as a Library Project instead of a .jar
|
||||
* CB-5302 Massive movement to get tests working again
|
||||
* CB-4996 Fix paths with spaces while launching on emulator and device
|
||||
* CB-5209 Cannot build Android app if project path contains spaces
|
||||
|
||||
|
||||
### 3.2.0 (Nov 2013) ###
|
||||
|
||||
27 commits from 7 authors. Highlights include:
|
||||
|
||||
* CB-5193 Fix Android WebSQL sometime throwing SECURITY_ERR.
|
||||
* CB-5191 Deprecate <url-filter>
|
||||
* Updating shelljs to 0.2.6. Copy now preserves mode bits.
|
||||
* CB-4872 Added android version scripts (android_sdk_version, etc)
|
||||
* CB-5117 Output confirmation message if check_reqs passes.
|
||||
* CB-5080 Find resources in a way that works with aapt's --rename-manifest-package
|
||||
* CB-4527 Don't delete .bat files even when on non-windows platform
|
||||
* CB-4892 Fix create script only escaping the first space instead of all spaces.
|
||||
|
||||
### 3.1.0 (Sept 2013) ###
|
||||
|
||||
@@ -32,7 +400,7 @@
|
||||
* [CB-4764] Deprecated DirectoryManager.java (moved into plugins)
|
||||
* [CB-4763] Deprecated FileHelper.java (moved into plugins), Move getMimeType() into CordovaResourceApi.
|
||||
* [CB-4725] Add CordovaWebView.CORDOVA_VERSION constant
|
||||
* Incremeting version check for Android 4.3 API Level 18
|
||||
* Incrementing version check for Android 4.3 API Level 18
|
||||
* [CB-3542] rewrote cli tooling scripts in node
|
||||
* Allow CordovaChromeClient subclasses access to CordovaInterface and CordovaWebView members
|
||||
* Refactor CordovaActivity.init so that subclasses can easily override factory methods for webview objects
|
||||
|
||||
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']).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,308 @@
|
||||
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) {
|
||||
return /^javac ((?:\d+\.)+(?:\d+))/i.exec(output)[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,39 +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 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, 'build.gradle'), projectPath);
|
||||
}
|
||||
|
||||
function copyScripts(projectPath) {
|
||||
@@ -95,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]
|
||||
*
|
||||
@@ -111,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) {
|
||||
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';
|
||||
@@ -128,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');
|
||||
@@ -140,18 +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');
|
||||
}
|
||||
|
||||
// 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...');
|
||||
@@ -160,11 +236,12 @@ 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(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'));
|
||||
|
||||
// copy cordova.js, cordova.jar and res/xml
|
||||
shell.cp('-r', path.join(ROOT, 'framework', 'res', 'xml'), path.join(project_path, 'res'));
|
||||
// copy cordova.js, cordova.jar
|
||||
copyJsAndLibrary(project_path, use_shared_project, safe_activity_name);
|
||||
|
||||
// interpolate the activity name and package
|
||||
@@ -179,27 +256,57 @@ 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);
|
||||
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.');
|
||||
});
|
||||
}
|
||||
|
||||
// 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()
|
||||
.then(function() {
|
||||
var target_api = check_reqs.get_target();
|
||||
copyJsAndLibrary(projectPath, false, null);
|
||||
copyScripts(projectPath);
|
||||
return runAndroidUpdate(projectPath, target_api, false)
|
||||
.then(function() {
|
||||
console.log('Android project is now at version ' + version);
|
||||
});
|
||||
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).
|
||||
function removeDebuggableFromManifest(projectPath) {
|
||||
var manifestPath = path.join(projectPath, 'AndroidManifest.xml');
|
||||
shell.sed('-i', /\s*android:debuggable="true"/, '', manifestPath);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
// Returns a promise.
|
||||
exports.updateProject = function(projectPath, shared) {
|
||||
return Q()
|
||||
.then(function() {
|
||||
var projectName = extractProjectNameFromManifest(projectPath);
|
||||
var target_api = check_reqs.get_target();
|
||||
copyJsAndLibrary(projectPath, shared, projectName);
|
||||
copyScripts(projectPath);
|
||||
copyBuildRules(projectPath);
|
||||
removeDebuggableFromManifest(projectPath);
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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 === ""
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
{
|
||||
"name": "cordova-android",
|
||||
"description": "Cordova tooling for the android platform.",
|
||||
"version": "0.0.0",
|
||||
"homepage": "http://github.com/apache/cordova-android",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git-wip-us.apache.org/repos/asf/cordova-android.git"
|
||||
},
|
||||
"keywords": [
|
||||
"cli",
|
||||
"cordova",
|
||||
"tooling"
|
||||
],
|
||||
"engineStrict": "true",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"shelljs" : "0.2.6",
|
||||
"q": "~0.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"optionalDependencies": {
|
||||
},
|
||||
"author": {
|
||||
"name": "Benn Mapes",
|
||||
"email": "bennmapes@gmail.com"
|
||||
},
|
||||
"contributors": [
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
26
bin/templates/cordova/defaults.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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.
|
||||
-->
|
||||
<widget xmlns = "http://www.w3.org/ns/widgets"
|
||||
id = "io.cordova.helloCordova"
|
||||
version = "2.0.0">
|
||||
|
||||
<!-- Preferences for Android -->
|
||||
<preference name="loglevel" value="DEBUG" />
|
||||
</widget>
|
||||
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());
|
||||
};
|
||||
|
||||
724
bin/templates/cordova/lib/build.js
vendored
@@ -19,69 +19,699 @@
|
||||
under the License.
|
||||
*/
|
||||
|
||||
/* jshint sub:true */
|
||||
|
||||
var shell = require('shelljs'),
|
||||
exec = require('./exec'),
|
||||
spawn = require('./spawn'),
|
||||
Q = require('q'),
|
||||
clean = require('./clean'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
os = require('os'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
var check_reqs = require('./check_reqs');
|
||||
var exec = require('./exec');
|
||||
|
||||
/*
|
||||
* Builds the project with ant.
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function(build_type) {
|
||||
//default build type
|
||||
build_type = typeof build_type !== 'undefined' ? build_type : "--debug";
|
||||
var cmd;
|
||||
switch(build_type) {
|
||||
case '--debug' :
|
||||
cmd = 'ant debug -f "' + path.join(ROOT, 'build.xml') + '"';
|
||||
break;
|
||||
case '--release' :
|
||||
cmd = 'ant release -f "' + path.join(ROOT, 'build.xml') + '"';
|
||||
break;
|
||||
case '--nobuild' :
|
||||
console.log('Skipping build...');
|
||||
return Q();
|
||||
default :
|
||||
return Q.reject('Build option \'' + build_type + '\' not recognized.');
|
||||
}
|
||||
if(cmd) {
|
||||
return clean.run() // TODO: Can we stop cleaning every time and let ant build incrementally?
|
||||
.then(function() {
|
||||
return exec(cmd);
|
||||
|
||||
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 Q();
|
||||
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'));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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?)
|
||||
* Builds the project with the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.get_apk = function() {
|
||||
if(fs.existsSync(path.join(ROOT, 'bin'))) {
|
||||
var bin_files = fs.readdirSync(path.join(ROOT, 'bin'));
|
||||
for (file in bin_files) {
|
||||
if(path.extname(bin_files[file]) == '.apk') {
|
||||
return path.join(ROOT, 'bin', bin_files[file]);
|
||||
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 the specifed options
|
||||
* Returns a promise.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
console.error('ERROR : No .apk found in \'bin\' folder');
|
||||
process.exit(2);
|
||||
} else {
|
||||
console.error('ERROR : unable to find project bin folder, could not locate .apk');
|
||||
process.exit(2);
|
||||
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();
|
||||
};
|
||||
|
||||
/*
|
||||
* Detects the architecture of a device/emulator
|
||||
* Returns "arm" or "x86".
|
||||
*/
|
||||
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';
|
||||
});
|
||||
}
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
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, 'corodva', '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);
|
||||
}
|
||||
};
|
||||
|
||||
38
bin/templates/cordova/lib/clean.js
vendored
@@ -1,38 +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 exec = require('./exec'),
|
||||
path = require('path'),
|
||||
ROOT = path.join(__dirname, '..', '..');
|
||||
|
||||
/*
|
||||
* Cleans the project using ant
|
||||
* Returns a promise.
|
||||
*/
|
||||
module.exports.run = function() {
|
||||
return exec('ant clean -f "' + path.join(ROOT, 'build.xml') + '"');
|
||||
}
|
||||
|
||||
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
@@ -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('LANCH 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);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
194
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,13 +141,13 @@ module.exports.list_targets = function() {
|
||||
}
|
||||
return targets;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Starts an emulator with the given ID,
|
||||
* and returns the started ID of that emulator.
|
||||
* If no ID is given it will used the first image availible,
|
||||
* if no image is availible it will error out (maybe create one?).
|
||||
* If no ID is given it will used the first image available,
|
||||
* if no image is available it will error out (maybe create one?).
|
||||
*
|
||||
* Returns a promise.
|
||||
*/
|
||||
@@ -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,25 +170,19 @@ 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 {
|
||||
return Q(emulator_ID);
|
||||
}
|
||||
}).then(function() {
|
||||
var cmd, args;
|
||||
if(process.platform == 'win32' || process.platform == 'win64') {
|
||||
cmd = '%comspec%';
|
||||
args = ['/c', 'start', 'cmd', '/c', 'emulator', '-avd', emulator_ID];
|
||||
} else {
|
||||
cmd = 'emulator';
|
||||
args = ['-avd', emulator_ID];
|
||||
}
|
||||
var cmd = 'emulator';
|
||||
var args = ['-avd', emulator_ID];
|
||||
var proc = child_process.spawn(cmd, args, { stdio: 'inherit', detached: true });
|
||||
proc.unref(); // Don't wait for it to finish, since the emulator will probably keep running for a long time.
|
||||
}).then(function() {
|
||||
@@ -194,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];
|
||||
}
|
||||
@@ -211,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.
|
||||
@@ -234,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;
|
||||
@@ -252,7 +249,7 @@ module.exports.wait_for_boot = function(emulator_id) {
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Create avd
|
||||
@@ -266,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.');
|
||||
@@ -274,23 +271,16 @@ module.exports.create_image = function(name, target) {
|
||||
.then(function() {
|
||||
// TODO: This seems like another error case, even though it always happens.
|
||||
console.error('ERROR : Unable to create an avd emulator, no targets found.');
|
||||
console.error('Please insure you have targets availible by runing the "android" command');
|
||||
console.error('Please insure you have targets available by running the "android" command');
|
||||
return Q.reject();
|
||||
}, function(error) {
|
||||
console.error('ERROR : Failed to create emulator image : ');
|
||||
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) {
|
||||
@@ -298,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);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
45
bin/templates/cordova/lib/exec.js
vendored
@@ -19,25 +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();
|
||||
console.log('exec: ' + cmd);
|
||||
|
||||
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}, function(err, stdout, stderr) {
|
||||
console.log([cmd, 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;
|
||||
};
|
||||
|
||||
|
||||
13
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, 'corodva', 'log')));
|
||||
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
@@ -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
@@ -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);
|
||||
}
|
||||
);
|
||||
};
|
||||
169
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 availible.
|
||||
* If not device is found, it will use a started emulator.
|
||||
* Runs the application on a device if available.
|
||||
* 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 {
|
||||
// no target given, deploy to device if availible, otherwise use the emulator.
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -21,23 +21,30 @@
|
||||
|
||||
var child_process = require('child_process'),
|
||||
Q = require('q');
|
||||
var isWindows = process.platform.slice(0, 3) == 'win';
|
||||
|
||||
// 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) {
|
||||
module.exports = function(cmd, args, opt_cwd) {
|
||||
var d = Q.defer();
|
||||
console.log('exec: ' + cmd);
|
||||
var opts = { cwd: opt_cwd, stdio: 'inherit' };
|
||||
try {
|
||||
child_process.exec(cmd, {cwd: opt_cwd}, function(err, stdout, stderr) {
|
||||
console.log([cmd, err, stdout, stderr]);
|
||||
if (err) d.reject('Error executing "' + cmd + '": ' + stderr);
|
||||
else d.resolve(stdout);
|
||||
// Work around spawn not being able to find .bat files.
|
||||
if (isWindows) {
|
||||
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, opts);
|
||||
child.on('exit', function(code) {
|
||||
if (code) {
|
||||
d.reject('Error code ' + code + ' for command: ' + cmd + ' with args: ' + args);
|
||||
} else {
|
||||
d.resolve();
|
||||
}
|
||||
});
|
||||
} catch(e) {
|
||||
console.error('error caught: ' + e);
|
||||
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.3.0-dev";
|
||||
var VERSION = "4.1.0";
|
||||
|
||||
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"
|
||||
@@ -29,19 +29,23 @@
|
||||
/>
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
|
||||
<application android:icon="@drawable/icon" android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true"
|
||||
android:debuggable="true">
|
||||
<activity android:name="__ACTIVITY__" android:label="@string/app_name"
|
||||
android: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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -31,7 +31,7 @@ var app = {
|
||||
// deviceready Event Handler
|
||||
//
|
||||
// The scope of 'this' is the event. In order to call the 'receivedEvent'
|
||||
// function, we must explicity call 'app.receivedEvent(...);'
|
||||
// function, we must explicitly call 'app.receivedEvent(...);'
|
||||
onDeviceReady: function() {
|
||||
app.receivedEvent('deviceready');
|
||||
},
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
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
@@ -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
|
||||
BIN
bin/templates/project/res/drawable-land-hdpi/screen.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
bin/templates/project/res/drawable-land-ldpi/screen.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
bin/templates/project/res/drawable-land-mdpi/screen.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
bin/templates/project/res/drawable-land-xhdpi/screen.png
Normal file
|
After Width: | Height: | Size: 478 KiB |
BIN
bin/templates/project/res/drawable-port-hdpi/screen.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
bin/templates/project/res/drawable-port-ldpi/screen.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
bin/templates/project/res/drawable-port-mdpi/screen.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
bin/templates/project/res/drawable-port-xhdpi/screen.png
Normal file
|
After Width: | Height: | Size: 493 KiB |
|
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,21 +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.xhr;
|
||||
var nativeApi = this._cordovaNative || require('cordova/android/promptbasednativeapi');
|
||||
var currentApi = nativeApi;
|
||||
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
|
||||
public class XhrTest extends ActivityInstrumentationTestCase2<xhr> {
|
||||
|
||||
public XhrTest()
|
||||
{
|
||||
super(xhr.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
@@ -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
@@ -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
@@ -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
@@ -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", []);
|
||||
}
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-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.LIBRARIES"/>
|
||||
<classpathentry kind="output" path="bin/classes"/>
|
||||
</classpath>
|
||||
|
||||
4
framework/.settings/org.eclipse.jdt.core.prefs
Normal file
@@ -0,0 +1,4 @@
|
||||
eclipse.preferences.version=1
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
|
||||
org.eclipse.jdt.core.compiler.compliance=1.6
|
||||
org.eclipse.jdt.core.compiler.source=1.6
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 4.0 KiB |
|
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,111 +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 java.net.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
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;
|
||||
|
||||
public Address(String uriHost, int uriPort, SSLSocketFactory sslSocketFactory,
|
||||
HostnameVerifier hostnameVerifier, Proxy proxy) throws UnknownHostException {
|
||||
if (uriHost == null) throw new NullPointerException("uriHost == null");
|
||||
if (uriPort <= 0) throw new IllegalArgumentException("uriPort <= 0: " + uriPort);
|
||||
this.proxy = proxy;
|
||||
this.uriHost = uriHost;
|
||||
this.uriPort = uriPort;
|
||||
this.sslSocketFactory = sslSocketFactory;
|
||||
this.hostnameVerifier = hostnameVerifier;
|
||||
}
|
||||
|
||||
/** 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 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);
|
||||
}
|
||||
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 + (proxy != null ? proxy.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,291 +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.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");
|
||||
}
|
||||
connected = true;
|
||||
socket = (route.proxy.type() != Proxy.Type.HTTP) ? new Socket(route.proxy) : new Socket();
|
||||
socket.connect(route.inetSocketAddress, connectTimeout);
|
||||
socket.setSoTimeout(readTimeout);
|
||||
in = socket.getInputStream();
|
||||
out = socket.getOutputStream();
|
||||
|
||||
if (route.address.sslSocketFactory != null) {
|
||||
upgradeToTls(tunnelRequest);
|
||||
}
|
||||
|
||||
// Use MTU-sized buffers to send fewer packets.
|
||||
int mtu = Platform.get().getMtu(socket);
|
||||
in = new BufferedInputStream(in, mtu);
|
||||
out = new BufferedOutputStream(out, mtu);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (route.modernTls) {
|
||||
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();
|
||||
|
||||
byte[] selectedProtocol;
|
||||
if (route.modernTls
|
||||
&& (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();
|
||||
} 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();
|
||||
}
|
||||
|
||||
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 isIdle() && System.nanoTime() - getIdleStartTimeNs() > 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +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>());
|
||||
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) {
|
||||
executorService.submit(connectionsCleanupCallable);
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,693 +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.HttpsURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.OkResponseCache;
|
||||
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.UnsupportedEncodingException;
|
||||
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.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
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.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
|
||||
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 {
|
||||
private static final char[] DIGITS =
|
||||
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
|
||||
// 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 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) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
byte[] md5bytes = messageDigest.digest(uri.toString().getBytes("UTF-8"));
|
||||
return bytesToHexString(md5bytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String bytesToHexString(byte[] bytes) {
|
||||
char[] digits = DIGITS;
|
||||
char[] buf = new char[bytes.length * 2];
|
||||
int c = 0;
|
||||
for (byte b : bytes) {
|
||||
buf[c++] = digits[(b >> 4) & 0xf];
|
||||
buf[c++] = digits[b & 0xf];
|
||||
}
|
||||
return new String(buf);
|
||||
}
|
||||
|
||||
@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();
|
||||
String key = uriToKey(uri);
|
||||
|
||||
if (requestMethod.equals("POST") || requestMethod.equals("PUT") || requestMethod.equals(
|
||||
"DELETE")) {
|
||||
try {
|
||||
cache.remove(key);
|
||||
} catch (IOException ignored) {
|
||||
// The cache cannot be written.
|
||||
}
|
||||
return null;
|
||||
} else 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(key);
|
||||
if (editor == null) {
|
||||
return null;
|
||||
}
|
||||
entry.writeTo(editor);
|
||||
return new CacheRequestImpl(editor);
|
||||
} catch (IOException e) {
|
||||
abortQuietly(editor);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (isHttps()) {
|
||||
HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
|
||||
cipherSuite = httpsConnection.getCipherSuite();
|
||||
Certificate[] peerCertificatesNonFinal = null;
|
||||
try {
|
||||
peerCertificatesNonFinal = httpsConnection.getServerCertificates();
|
||||
} catch (SSLPeerUnverifiedException ignored) {
|
||||
}
|
||||
peerCertificates = peerCertificatesNonFinal;
|
||||
localCertificates = httpsConnection.getLocalCertificates();
|
||||
} else {
|
||||
cipherSuite = null;
|
||||
peerCertificates = null;
|
||||
localCertificates = 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,216 +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.http.HttpURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.HttpsURLConnectionImpl;
|
||||
import com.squareup.okhttp.internal.http.OkResponseCache;
|
||||
import com.squareup.okhttp.internal.http.OkResponseCacheAdapter;
|
||||
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.util.Collections;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Set;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/** Configures and creates HTTP connections. */
|
||||
public final class OkHttpClient {
|
||||
private Proxy proxy;
|
||||
private Set<Route> failedRoutes = Collections.synchronizedSet(new LinkedHashSet<Route>());
|
||||
private ProxySelector proxySelector;
|
||||
private CookieHandler cookieHandler;
|
||||
private ResponseCache responseCache;
|
||||
private SSLSocketFactory sslSocketFactory;
|
||||
private HostnameVerifier hostnameVerifier;
|
||||
private ConnectionPool connectionPool;
|
||||
private boolean followProtocolRedirects = true;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
private OkResponseCache okResponseCache() {
|
||||
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 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 HttpURLConnection open(URL url) {
|
||||
String protocol = url.getProtocol();
|
||||
OkHttpClient copy = copyWithDefaults();
|
||||
if (protocol.equals("http")) {
|
||||
return new HttpURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
|
||||
} else if (protocol.equals("https")) {
|
||||
return new HttpsURLConnectionImpl(url, copy, copy.okResponseCache(), copy.failedRoutes);
|
||||
} else {
|
||||
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();
|
||||
result.proxy = proxy;
|
||||
result.failedRoutes = failedRoutes;
|
||||
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
|
||||
: HttpsURLConnection.getDefaultHostnameVerifier();
|
||||
result.connectionPool = connectionPool != null ? connectionPool : ConnectionPool.getDefault();
|
||||
result.followProtocolRedirects = followProtocolRedirects;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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 java.io.IOException;
|
||||
import java.net.CacheResponse;
|
||||
import java.net.HttpURLConnection;
|
||||
|
||||
/**
|
||||
* A response cache that supports statistics tracking and updating stored
|
||||
* responses. Implementations of {@link java.net.ResponseCache} should implement
|
||||
* this interface to receive additional support from the HTTP engine.
|
||||
*/
|
||||
public interface OkResponseCache {
|
||||
|
||||
/** Track an HTTP response being satisfied by {@code source}. */
|
||||
void trackResponse(ResponseSource source);
|
||||
|
||||
/** Track an conditional GET that was satisfied by this cache. */
|
||||
void trackConditionalCacheHit();
|
||||
|
||||
/** Updates stored HTTP headers using a hit on a conditional GET. */
|
||||
void update(CacheResponse conditionalCacheHit, HttpURLConnection httpConnection)
|
||||
throws IOException;
|
||||
}
|
||||
@@ -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. */
|
||||
public 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,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 String name;
|
||||
|
||||
public NamedRunnable(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2012 Square, Inc.
|
||||
* 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.internal;
|
||||
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationHandler;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
/**
|
||||
* Access to Platform-specific features necessary for SPDY and advanced TLS.
|
||||
*
|
||||
* <h3>SPDY</h3>
|
||||
* SPDY requires a TLS extension called NPN (Next Protocol Negotiation) that's
|
||||
* available in Android 4.1+ and OpenJDK 7+ (with the npn-boot extension). It
|
||||
* also requires a recent version of {@code DeflaterOutputStream} that is
|
||||
* public API in Java 7 and callable via reflection in Android 4.1+.
|
||||
*/
|
||||
public class Platform {
|
||||
private static final Platform PLATFORM = findPlatform();
|
||||
|
||||
private Constructor<DeflaterOutputStream> deflaterConstructor;
|
||||
|
||||
public static Platform get() {
|
||||
return PLATFORM;
|
||||
}
|
||||
|
||||
public void logW(String warning) {
|
||||
System.out.println(warning);
|
||||
}
|
||||
|
||||
public void tagSocket(Socket socket) throws SocketException {
|
||||
}
|
||||
|
||||
public void untagSocket(Socket socket) throws SocketException {
|
||||
}
|
||||
|
||||
public URI toUriLenient(URL url) throws URISyntaxException {
|
||||
return url.toURI(); // this isn't as good as the built-in toUriLenient
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt a TLS connection with useful extensions enabled. This mode
|
||||
* supports more features, but is less likely to be compatible with older
|
||||
* HTTPS servers.
|
||||
*/
|
||||
public void enableTlsExtensions(SSLSocket socket, String uriHost) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt a secure connection with basic functionality to maximize
|
||||
* compatibility. Currently this uses SSL 3.0.
|
||||
*/
|
||||
public void supportTlsIntolerantServer(SSLSocket socket) {
|
||||
socket.setEnabledProtocols(new String[] {"SSLv3"});
|
||||
}
|
||||
|
||||
/** Returns the negotiated protocol, or null if no protocol was negotiated. */
|
||||
public byte[] getNpnSelectedProtocol(SSLSocket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets client-supported protocols on a socket to send to a server. The
|
||||
* protocols are only sent if the socket implementation supports NPN.
|
||||
*/
|
||||
public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a deflater output stream that supports SYNC_FLUSH for SPDY name
|
||||
* value blocks. This throws an {@link UnsupportedOperationException} on
|
||||
* Java 6 and earlier where there is no built-in API to do SYNC_FLUSH.
|
||||
*/
|
||||
public OutputStream newDeflaterOutputStream(OutputStream out, Deflater deflater,
|
||||
boolean syncFlush) {
|
||||
try {
|
||||
Constructor<DeflaterOutputStream> constructor = deflaterConstructor;
|
||||
if (constructor == null) {
|
||||
constructor = deflaterConstructor = DeflaterOutputStream.class.getConstructor(
|
||||
OutputStream.class, Deflater.class, boolean.class);
|
||||
}
|
||||
return constructor.newInstance(out, deflater, syncFlush);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new UnsupportedOperationException("Cannot SPDY; no SYNC_FLUSH available");
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause() instanceof RuntimeException ? (RuntimeException) e.getCause()
|
||||
: new RuntimeException(e.getCause());
|
||||
} catch (InstantiationException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum transmission unit of the network interface used by
|
||||
* {@code socket}, or a reasonable default if this platform doesn't expose the
|
||||
* MTU to the application layer.
|
||||
*
|
||||
* <p>The returned value should only be used as an optimization; such as to
|
||||
* size buffers efficiently.
|
||||
*/
|
||||
public int getMtu(Socket socket) throws IOException {
|
||||
return 1400; // Smaller than 1500 to leave room for headers on interfaces like PPPoE.
|
||||
}
|
||||
|
||||
/** Attempt to match the host runtime to a capable Platform implementation. */
|
||||
private static Platform findPlatform() {
|
||||
Method getMtu;
|
||||
try {
|
||||
getMtu = NetworkInterface.class.getMethod("getMTU");
|
||||
} catch (NoSuchMethodException e) {
|
||||
return new Platform(); // No Java 1.6 APIs. It's either Java 1.5, Android 2.2 or earlier.
|
||||
}
|
||||
|
||||
// Attempt to find Android 2.3+ APIs.
|
||||
Class<?> openSslSocketClass;
|
||||
Method setUseSessionTickets;
|
||||
Method setHostname;
|
||||
try {
|
||||
openSslSocketClass = Class.forName("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl");
|
||||
setUseSessionTickets = openSslSocketClass.getMethod("setUseSessionTickets", boolean.class);
|
||||
setHostname = openSslSocketClass.getMethod("setHostname", String.class);
|
||||
|
||||
// Attempt to find Android 4.1+ APIs.
|
||||
try {
|
||||
Method setNpnProtocols = openSslSocketClass.getMethod("setNpnProtocols", byte[].class);
|
||||
Method getNpnSelectedProtocol = openSslSocketClass.getMethod("getNpnSelectedProtocol");
|
||||
return new Android41(getMtu, openSslSocketClass, setUseSessionTickets, setHostname,
|
||||
setNpnProtocols, getNpnSelectedProtocol);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
return new Android23(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
|
||||
}
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// This isn't an Android runtime.
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// This isn't Android 2.3 or better.
|
||||
}
|
||||
|
||||
// Attempt to find the Jetty's NPN extension for OpenJDK.
|
||||
try {
|
||||
String npnClassName = "org.eclipse.jetty.npn.NextProtoNego";
|
||||
Class<?> nextProtoNegoClass = Class.forName(npnClassName);
|
||||
Class<?> providerClass = Class.forName(npnClassName + "$Provider");
|
||||
Class<?> clientProviderClass = Class.forName(npnClassName + "$ClientProvider");
|
||||
Class<?> serverProviderClass = Class.forName(npnClassName + "$ServerProvider");
|
||||
Method putMethod = nextProtoNegoClass.getMethod("put", SSLSocket.class, providerClass);
|
||||
Method getMethod = nextProtoNegoClass.getMethod("get", SSLSocket.class);
|
||||
return new JdkWithJettyNpnPlatform(getMtu, putMethod, getMethod, clientProviderClass,
|
||||
serverProviderClass);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// NPN isn't on the classpath.
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
// The NPN version isn't what we expect.
|
||||
}
|
||||
|
||||
return getMtu != null ? new Java5(getMtu) : new Platform();
|
||||
}
|
||||
|
||||
private static class Java5 extends Platform {
|
||||
private final Method getMtu;
|
||||
|
||||
private Java5(Method getMtu) {
|
||||
this.getMtu = getMtu;
|
||||
}
|
||||
|
||||
@Override public int getMtu(Socket socket) throws IOException {
|
||||
try {
|
||||
NetworkInterface networkInterface = NetworkInterface.getByInetAddress(
|
||||
socket.getLocalAddress());
|
||||
return (Integer) getMtu.invoke(networkInterface);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
if (e.getCause() instanceof IOException) throw (IOException) e.getCause();
|
||||
throw new RuntimeException(e.getCause());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Android version 2.3 and newer support TLS session tickets and server name
|
||||
* indication (SNI).
|
||||
*/
|
||||
private static class Android23 extends Java5 {
|
||||
protected final Class<?> openSslSocketClass;
|
||||
private final Method setUseSessionTickets;
|
||||
private final Method setHostname;
|
||||
|
||||
private Android23(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
|
||||
Method setHostname) {
|
||||
super(getMtu);
|
||||
this.openSslSocketClass = openSslSocketClass;
|
||||
this.setUseSessionTickets = setUseSessionTickets;
|
||||
this.setHostname = setHostname;
|
||||
}
|
||||
|
||||
@Override public void enableTlsExtensions(SSLSocket socket, String uriHost) {
|
||||
super.enableTlsExtensions(socket, uriHost);
|
||||
if (openSslSocketClass.isInstance(socket)) {
|
||||
// This is Android: use reflection on OpenSslSocketImpl.
|
||||
try {
|
||||
setUseSessionTickets.invoke(socket, true);
|
||||
setHostname.invoke(socket, uriHost);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Android version 4.1 and newer support NPN. */
|
||||
private static class Android41 extends Android23 {
|
||||
private final Method setNpnProtocols;
|
||||
private final Method getNpnSelectedProtocol;
|
||||
|
||||
private Android41(Method getMtu, Class<?> openSslSocketClass, Method setUseSessionTickets,
|
||||
Method setHostname, Method setNpnProtocols, Method getNpnSelectedProtocol) {
|
||||
super(getMtu, openSslSocketClass, setUseSessionTickets, setHostname);
|
||||
this.setNpnProtocols = setNpnProtocols;
|
||||
this.getNpnSelectedProtocol = getNpnSelectedProtocol;
|
||||
}
|
||||
|
||||
@Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
|
||||
if (!openSslSocketClass.isInstance(socket)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setNpnProtocols.invoke(socket, new Object[] {npnProtocols});
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
|
||||
if (!openSslSocketClass.isInstance(socket)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (byte[]) getNpnSelectedProtocol.invoke(socket);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenJDK 7 plus {@code org.mortbay.jetty.npn/npn-boot} on the boot class
|
||||
* path.
|
||||
*/
|
||||
private static class JdkWithJettyNpnPlatform extends Java5 {
|
||||
private final Method getMethod;
|
||||
private final Method putMethod;
|
||||
private final Class<?> clientProviderClass;
|
||||
private final Class<?> serverProviderClass;
|
||||
|
||||
public JdkWithJettyNpnPlatform(Method getMtu, Method putMethod, Method getMethod,
|
||||
Class<?> clientProviderClass, Class<?> serverProviderClass) {
|
||||
super(getMtu);
|
||||
this.putMethod = putMethod;
|
||||
this.getMethod = getMethod;
|
||||
this.clientProviderClass = clientProviderClass;
|
||||
this.serverProviderClass = serverProviderClass;
|
||||
}
|
||||
|
||||
@Override public void setNpnProtocols(SSLSocket socket, byte[] npnProtocols) {
|
||||
try {
|
||||
List<String> strings = new ArrayList<String>();
|
||||
for (int i = 0; i < npnProtocols.length; ) {
|
||||
int length = npnProtocols[i++];
|
||||
strings.add(new String(npnProtocols, i, length, "US-ASCII"));
|
||||
i += length;
|
||||
}
|
||||
Object provider = Proxy.newProxyInstance(Platform.class.getClassLoader(),
|
||||
new Class[] {clientProviderClass, serverProviderClass},
|
||||
new JettyNpnProvider(strings));
|
||||
putMethod.invoke(null, socket, provider);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override public byte[] getNpnSelectedProtocol(SSLSocket socket) {
|
||||
try {
|
||||
JettyNpnProvider provider =
|
||||
(JettyNpnProvider) Proxy.getInvocationHandler(getMethod.invoke(null, socket));
|
||||
if (!provider.unsupported && provider.selected == null) {
|
||||
Logger logger = Logger.getLogger(OkHttpClient.class.getName());
|
||||
logger.log(Level.INFO,
|
||||
"NPN callback dropped so SPDY is disabled. " + "Is npn-boot on the boot class path?");
|
||||
return null;
|
||||
}
|
||||
return provider.unsupported ? null : provider.selected.getBytes("US-ASCII");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError();
|
||||
} catch (InvocationTargetException e) {
|
||||
throw new AssertionError();
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the methods of NextProtoNego's ClientProvider and ServerProvider
|
||||
* without a compile-time dependency on those interfaces.
|
||||
*/
|
||||
private static class JettyNpnProvider implements InvocationHandler {
|
||||
private final List<String> protocols;
|
||||
private boolean unsupported;
|
||||
private String selected;
|
||||
|
||||
public JettyNpnProvider(List<String> protocols) {
|
||||
this.protocols = protocols;
|
||||
}
|
||||
|
||||
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
String methodName = method.getName();
|
||||
Class<?> returnType = method.getReturnType();
|
||||
if (args == null) {
|
||||
args = Util.EMPTY_STRING_ARRAY;
|
||||
}
|
||||
if (methodName.equals("supports") && boolean.class == returnType) {
|
||||
return true;
|
||||
} else if (methodName.equals("unsupported") && void.class == returnType) {
|
||||
this.unsupported = true;
|
||||
return null;
|
||||
} else if (methodName.equals("protocols") && args.length == 0) {
|
||||
return protocols;
|
||||
} else if (methodName.equals("selectProtocol")
|
||||
&& String.class == returnType
|
||||
&& args.length == 1
|
||||
&& (args[0] == null || args[0] instanceof List)) {
|
||||
// TODO: use OpenSSL's algorithm which uses both lists
|
||||
List<?> serverProtocols = (List) args[0];
|
||||
this.selected = protocols.get(0);
|
||||
return selected;
|
||||
} else if (methodName.equals("protocolSelected") && args.length == 1) {
|
||||
this.selected = (String) args[0];
|
||||
return null;
|
||||
} else {
|
||||
return method.invoke(this, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||