Compare commits
1158 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a39e78a989 | ||
|
|
5a461d68a7 | ||
|
|
899816673c | ||
|
|
84a10139c1 | ||
|
|
da3aa56d86 | ||
|
|
a6dcd512ea | ||
|
|
572d32c1de | ||
|
|
e8b67ead1e | ||
|
|
377029226e | ||
|
|
72d1421f1d | ||
|
|
d359547dab | ||
|
|
9523c7ab33 | ||
|
|
7cac07c185 | ||
|
|
a607a7f3ef | ||
|
|
b5277e89d5 | ||
|
|
99713f8ed7 | ||
|
|
145dac500c | ||
|
|
eefd9bf31c | ||
|
|
3f47ca645b | ||
|
|
c906f037b5 | ||
|
|
ffc4e32119 | ||
|
|
51eb46241b | ||
|
|
541416f64b | ||
|
|
14d1d132a3 | ||
|
|
22a0045796 | ||
|
|
2e5ba0335d | ||
|
|
3315704c14 | ||
|
|
f90c407fb6 | ||
|
|
d3646e10a5 | ||
|
|
d3003efe72 | ||
|
|
235ca947be | ||
|
|
e7bc9ed5ba | ||
|
|
740a18dbc0 | ||
|
|
add5189bb1 | ||
|
|
62002b8bb3 | ||
|
|
41b4ee33fe | ||
|
|
85c67ac676 | ||
|
|
4637b7d93f | ||
|
|
8b9a178c87 | ||
|
|
c595c381a9 | ||
|
|
c308d7a610 | ||
|
|
f7570122c6 | ||
|
|
1e5f186b58 | ||
|
|
b732b2ffa7 | ||
|
|
617a7be4f3 | ||
|
|
5c5e368d6b | ||
|
|
ff9f49416a | ||
|
|
93cae0e9cc | ||
|
|
61e75ce747 | ||
|
|
50affe26c5 | ||
|
|
8566362607 | ||
|
|
36925770d0 | ||
|
|
d17ca1686e | ||
|
|
2ad709dae4 | ||
|
|
5a3d86a12a | ||
|
|
1a4ba36820 | ||
|
|
ddeae3b5ba | ||
|
|
10df9e7cd5 | ||
|
|
be11933c60 | ||
|
|
d2db68b781 | ||
|
|
da6e271584 | ||
|
|
5103c80e1e | ||
|
|
b5c80ea267 | ||
|
|
b5747fbb44 | ||
|
|
215ded8a77 | ||
|
|
e7ab6da068 | ||
|
|
ee881f67ee | ||
|
|
f33248aa4f | ||
|
|
c6a40bac03 | ||
|
|
aa38e79d08 | ||
|
|
9cac10e559 | ||
|
|
efbd418f56 | ||
|
|
4ff3464abd | ||
|
|
907ddbf903 | ||
|
|
7041991d5a | ||
|
|
b26067e5da | ||
|
|
e519b13533 | ||
|
|
30bc3867bf | ||
|
|
5326c3aecc | ||
|
|
c95251c903 | ||
|
|
e08a0a0938 | ||
|
|
fcb072c37d | ||
|
|
262c19b194 | ||
|
|
1031ddcd83 | ||
|
|
aaee0212f0 | ||
|
|
8fc95759ba | ||
|
|
b6a3a0a54f | ||
|
|
e3091be314 | ||
|
|
045d0678b3 | ||
|
|
e3eeaddb3e | ||
|
|
f2b202c714 | ||
|
|
e6f3ad60ef | ||
|
|
fb6e0c9eb8 | ||
|
|
fb6e488339 | ||
|
|
0bccc8f0d5 | ||
|
|
560d831e92 | ||
|
|
7c8f6a1cc7 | ||
|
|
deb1ead4ea | ||
|
|
005b2a4fb6 | ||
|
|
4c8204f29a | ||
|
|
83fd19784a | ||
|
|
1f21a2ecc7 | ||
|
|
c3f4d56d1e | ||
|
|
7e4f79bacb | ||
|
|
b8267d4329 | ||
|
|
205ca693b3 | ||
|
|
23159c2201 | ||
|
|
884521ced0 | ||
|
|
aeb01ba292 | ||
|
|
2b1a556e9e | ||
|
|
d5c3ae3d19 | ||
|
|
7654bb7088 | ||
|
|
64c301caeb | ||
|
|
e875b530b1 | ||
|
|
ee8c2b5272 | ||
|
|
9b8bdb0639 | ||
|
|
1a99bbb040 | ||
|
|
0982b45473 | ||
|
|
bcd210d504 | ||
|
|
c71608824b | ||
|
|
170876ac16 | ||
|
|
a6b172c445 | ||
|
|
e625450100 | ||
|
|
fca5447094 | ||
|
|
89f32beec5 | ||
|
|
65eb7662ad | ||
|
|
ea090dbe18 | ||
|
|
5750817620 | ||
|
|
93386d0e8b | ||
|
|
e9dd7b8f98 | ||
|
|
5010e7cb7a | ||
|
|
fb35f4fbca | ||
|
|
00c0a93a6b | ||
|
|
cd9a312e0b | ||
|
|
8cdb27fe43 | ||
|
|
d84d11d064 | ||
|
|
5d646a6112 | ||
|
|
dd334858ff | ||
|
|
dfd39ebc95 | ||
|
|
9dbb1f15a4 | ||
|
|
32fd04a7d9 | ||
|
|
3a33ff375d | ||
|
|
8877e71bd2 | ||
|
|
5b5385c01d | ||
|
|
9c365ecc48 | ||
|
|
99d1a6d043 | ||
|
|
854ef7e645 | ||
|
|
a8eeb12325 | ||
|
|
86a3f516b1 | ||
|
|
d6818939b3 | ||
|
|
c51449e607 | ||
|
|
333894ddeb | ||
|
|
052e227b65 | ||
|
|
4dd6df5bbe | ||
|
|
8847991bba | ||
|
|
0ffc6e4a1a | ||
|
|
9a399e06f3 | ||
|
|
6afccc2aea | ||
|
|
8cd3a21468 | ||
|
|
8b6d2d2b83 | ||
|
|
a39f4d5987 | ||
|
|
e236d045b0 | ||
|
|
592c7951df | ||
|
|
41efa96291 | ||
|
|
53f406a267 | ||
|
|
1390df48b6 | ||
|
|
01878ef00c | ||
|
|
6f119f25f4 | ||
|
|
3f31d78db1 | ||
|
|
014bf7777b | ||
|
|
7191969e9c | ||
|
|
0950510526 | ||
|
|
9eba3064a7 | ||
|
|
a4d785258e | ||
|
|
efef49f976 | ||
|
|
32a7b5bafb | ||
|
|
5fa080063c | ||
|
|
b3573f9482 | ||
|
|
916b7ee46b | ||
|
|
bfe8ad2034 | ||
|
|
bb9db7bf9d | ||
|
|
d7f7c8c568 | ||
|
|
10839588dd | ||
|
|
12cd2b67cd | ||
|
|
9a13036f4e | ||
|
|
2f025d51ff | ||
|
|
6cb6d3a358 | ||
|
|
0bb01d14bd | ||
|
|
2229c4e4d1 | ||
|
|
5c90c62378 | ||
|
|
b7ed0a29fe | ||
|
|
796c2ed58c | ||
|
|
39bd573d9d | ||
|
|
d4aaa547a7 | ||
|
|
f7e2382847 | ||
|
|
4bb16e7d93 | ||
|
|
e7e00e4ebf | ||
|
|
55d050fca7 | ||
|
|
98d4fef0ee | ||
|
|
5521892736 | ||
|
|
8f0fd0dfef | ||
|
|
514eab0b98 | ||
|
|
e35c84245d | ||
|
|
4a2391eb1b | ||
|
|
04eb497e10 | ||
|
|
a9a5da6dc6 | ||
|
|
3f1aab27d6 | ||
|
|
8f77df4ebb | ||
|
|
65a8b3fcd4 | ||
|
|
71dd4e512e | ||
|
|
ca03a5ecf4 | ||
|
|
694efb9508 | ||
|
|
c60a112039 | ||
|
|
c3570dd07a | ||
|
|
b680b190d4 | ||
|
|
310d51d859 | ||
|
|
cf9e820227 | ||
|
|
b91d2f4fb1 | ||
|
|
9456f5dc31 | ||
|
|
fa9e22730a | ||
|
|
7261fd7ed9 | ||
|
|
ad9e5fbf07 | ||
|
|
c242117230 | ||
|
|
d7c1b23fa2 | ||
|
|
25e9919bb3 | ||
|
|
912c01457a | ||
|
|
8083390eab | ||
|
|
44baa4cc1e | ||
|
|
f0662bb878 | ||
|
|
fbe1a6d4c5 | ||
|
|
2235f1f7af | ||
|
|
3f46f83ec8 | ||
|
|
d537d4a27e | ||
|
|
c67250da2d | ||
|
|
6027406eef | ||
|
|
fdc51f33ad | ||
|
|
ea7290afab | ||
|
|
be65597d57 | ||
|
|
253ed75800 | ||
|
|
e4f3671ae0 | ||
|
|
c60cefd188 | ||
|
|
175a41f275 | ||
|
|
bd5fd72459 | ||
|
|
98b70a647b | ||
|
|
3eee5e696d | ||
|
|
d92c6cc6c6 | ||
|
|
488485da54 | ||
|
|
a3f0254fb2 | ||
|
|
ab5f1385c5 | ||
|
|
cde5b09943 | ||
|
|
db42b6a3a5 | ||
|
|
ece35b96db | ||
|
|
c4c24ee240 | ||
|
|
c7ba465970 | ||
|
|
937ad444da | ||
|
|
0c0a928e87 | ||
|
|
2823a86b4e | ||
|
|
1a06683611 | ||
|
|
50fa74adfe | ||
|
|
4ebd249356 | ||
|
|
4dc388015c | ||
|
|
0270cf6e45 | ||
|
|
7a19c50ec0 | ||
|
|
e2fc5fff23 | ||
|
|
839df123ff | ||
|
|
f897193f79 | ||
|
|
9f23f4ead7 | ||
|
|
45ab6d47de | ||
|
|
35bc94f4bd | ||
|
|
94a4a38798 | ||
|
|
7f431dbd01 | ||
|
|
d0257df134 | ||
|
|
be3ed16d3c | ||
|
|
b651becf66 | ||
|
|
bcf49ab396 | ||
|
|
fb76ecf198 | ||
|
|
1bed49b4c6 | ||
|
|
c34376820a | ||
|
|
582e6ee322 | ||
|
|
2b4ffaa357 | ||
|
|
379a82972a | ||
|
|
713796a4f7 | ||
|
|
54161aaf39 | ||
|
|
4054519f38 | ||
|
|
e5d5d8b434 | ||
|
|
519fd212d9 | ||
|
|
4b21660fd6 | ||
|
|
ac5c9e7242 | ||
|
|
4c8431bd5b | ||
|
|
a4a93f0999 | ||
|
|
b6a4efa7ba | ||
|
|
a4fbd521e3 | ||
|
|
6fe5e89ecc | ||
|
|
bc40c95f20 | ||
|
|
acd35ac8a2 | ||
|
|
c9d9ec1c77 | ||
|
|
08c4e2d465 | ||
|
|
f300d1bafd | ||
|
|
d395115cc9 | ||
|
|
8571755daa | ||
|
|
9f3368ba1f | ||
|
|
919df1edd5 | ||
|
|
c180422e8b | ||
|
|
e90501a986 | ||
|
|
545af007b4 | ||
|
|
e189ece487 | ||
|
|
cebc2b5bdb | ||
|
|
444d48a259 | ||
|
|
4fd70ad252 | ||
|
|
293326b647 | ||
|
|
b071238eda | ||
|
|
09ef1b66cc | ||
|
|
c06df3889b | ||
|
|
280fc78f7e | ||
|
|
7b9fc04704 | ||
|
|
00e60f2592 | ||
|
|
45e9c762a7 | ||
|
|
77dcb91741 | ||
|
|
383c683716 | ||
|
|
ca3c380493 | ||
|
|
57ec5cb036 | ||
|
|
177b48ac90 | ||
|
|
b4e7fd6fa8 | ||
|
|
baf785d9f1 | ||
|
|
25b75fd6e4 | ||
|
|
0c4c8534b4 | ||
|
|
a559dbfe06 | ||
|
|
de20bb22d9 | ||
|
|
45c0ec9035 | ||
|
|
b16da90e42 | ||
|
|
ce7bcfa666 | ||
|
|
f6833699a6 | ||
|
|
040dc72877 | ||
|
|
056bce3dd9 | ||
|
|
5d6575e97b | ||
|
|
f092d4ffc3 | ||
|
|
5bae15831b | ||
|
|
cf19bd88f0 | ||
|
|
38ac6a1082 | ||
|
|
b88e2bd3ce | ||
|
|
fad24c4308 | ||
|
|
018fd83dba | ||
|
|
aa95da167f | ||
|
|
24e6a0be68 | ||
|
|
a2c962c2f6 | ||
|
|
aa61331181 | ||
|
|
00f0a7589c | ||
|
|
d39609351a | ||
|
|
6985ccf076 | ||
|
|
b448cad4de | ||
|
|
14540b4cc0 | ||
|
|
898b76a549 | ||
|
|
5cf6e382d8 | ||
|
|
e2ba56a227 | ||
|
|
ec9960e28e | ||
|
|
dc59283160 | ||
|
|
d255d44be5 | ||
|
|
b2f68a5a7e | ||
|
|
ec32679ab1 | ||
|
|
8b2471c128 | ||
|
|
022eba2c05 | ||
|
|
029c6fcfe3 | ||
|
|
faaa0b2488 | ||
|
|
daa2ca876b | ||
|
|
81700cfb44 | ||
|
|
6e58db95ed | ||
|
|
9b54862450 | ||
|
|
f79efadd82 | ||
|
|
615a7670bd | ||
|
|
155b8b472f | ||
|
|
b35e3454f0 | ||
|
|
51b4716d45 | ||
|
|
b62803a03a | ||
|
|
616893955f | ||
|
|
0f387a139b | ||
|
|
219c81aac5 | ||
|
|
083003d34f | ||
|
|
699f76c29e | ||
|
|
b670280688 | ||
|
|
37ea84ffe9 | ||
|
|
40b861acbe | ||
|
|
783c4d104c | ||
|
|
9bbddd6aeb | ||
|
|
e753acbc3f | ||
|
|
92b7b1d603 | ||
|
|
b07dc8443e | ||
|
|
3f99c513f3 | ||
|
|
793241523d | ||
|
|
7cff22fb9e | ||
|
|
214f308027 | ||
|
|
c1ce971adb | ||
|
|
f5896be699 | ||
|
|
186f839569 | ||
|
|
4879d906d9 | ||
|
|
09412f0b78 | ||
|
|
2f2d85576f | ||
|
|
362ddd0339 | ||
|
|
9499b7f562 | ||
|
|
d8bb12b5f1 | ||
|
|
5d464f4477 | ||
|
|
aaea0b2659 | ||
|
|
c9ceb09906 | ||
|
|
3b44ede67e | ||
|
|
b48e8eeb0e | ||
|
|
1fafc29ec3 | ||
|
|
1a9d0576c8 | ||
|
|
bc04211b79 | ||
|
|
cfe34355bd | ||
|
|
e3e833d8c0 | ||
|
|
5606a0a968 | ||
|
|
f0358f1da8 | ||
|
|
a3129e9e17 | ||
|
|
7435ede254 | ||
|
|
84e79e92b4 | ||
|
|
6268130998 | ||
|
|
7ad639599a | ||
|
|
caff67b77d | ||
|
|
c45a77d538 | ||
|
|
4b24fe1bf4 | ||
|
|
73e5fb6314 | ||
|
|
84ea28adfa | ||
|
|
955fc97cb2 | ||
|
|
e4012e4f87 | ||
|
|
2c662c428c | ||
|
|
da199deed1 | ||
|
|
abf75cffd9 | ||
|
|
184f13b148 | ||
|
|
d1c7309b29 | ||
|
|
62db6552d2 | ||
|
|
a019b9e1d3 | ||
|
|
cb22572f2b | ||
|
|
b52134e9ee | ||
|
|
44ef82219b | ||
|
|
8c89b0e587 | ||
|
|
322b251def | ||
|
|
0a6767209d | ||
|
|
1694b5d6fd | ||
|
|
0dd9ad43e8 | ||
|
|
c1ae3f1fb2 | ||
|
|
d84627aa2c | ||
|
|
0e020924ff | ||
|
|
4f5e238685 | ||
|
|
72ff680114 | ||
|
|
849ec6fa8f | ||
|
|
36ee3aaec6 | ||
|
|
497d4f50dd | ||
|
|
74a40b2274 | ||
|
|
75e85541a6 | ||
|
|
1d8fbac796 | ||
|
|
daf6d1936f | ||
|
|
1768e8cb62 | ||
|
|
d2d6bfc065 | ||
|
|
d4f6e9c587 | ||
|
|
6d06f2212e | ||
|
|
73b310d59e | ||
|
|
3dc705a7a9 | ||
|
|
8fc8d03cc4 | ||
|
|
df77f42145 | ||
|
|
0dea5bdbea | ||
|
|
5c7f939440 | ||
|
|
09bef28362 | ||
|
|
1f0f94746b | ||
|
|
c057be17d0 | ||
|
|
301aaf9c68 | ||
|
|
ab7093f962 | ||
|
|
29b2d67fb6 | ||
|
|
d4cd2b8be8 | ||
|
|
fea94f956d | ||
|
|
a656aa21f8 | ||
|
|
3b235f2ca2 | ||
|
|
376841f619 | ||
|
|
746a7c404b | ||
|
|
2d126300d8 | ||
|
|
ed7e43ed6e | ||
|
|
7c56f1a773 | ||
|
|
6ba396440f | ||
|
|
8970525861 | ||
|
|
5a88a66709 | ||
|
|
3ae6e3ee53 | ||
|
|
5c0d4700f9 | ||
|
|
7b354f5b8c | ||
|
|
a197c0219e | ||
|
|
05f4036309 | ||
|
|
37974c7ec8 | ||
|
|
5cb3e15201 | ||
|
|
54b4766680 | ||
|
|
cc0bb65096 | ||
|
|
296c9dc055 | ||
|
|
70aa2309b7 | ||
|
|
d2468d144e | ||
|
|
ebbe704672 | ||
|
|
d146870a74 | ||
|
|
58ebabf74c | ||
|
|
8f8a3b6387 | ||
|
|
df616cfe3e | ||
|
|
dd96608bb1 | ||
|
|
264f2ab316 | ||
|
|
773f156785 | ||
|
|
7cd3e2a5b9 | ||
|
|
0ec22a4639 | ||
|
|
74ac9cbbbe | ||
|
|
0020bd0fb7 | ||
|
|
1d6ec0f953 | ||
|
|
37f05f0a12 | ||
|
|
9a22a1dbf4 | ||
|
|
b768c8b28a | ||
|
|
bcbdbb4932 | ||
|
|
04e42c4a75 | ||
|
|
6040c7768f | ||
|
|
6da0d3e88d | ||
|
|
7c6cc7b246 | ||
|
|
d5da1d6f3f | ||
|
|
de5ee90e21 | ||
|
|
8a0c9ab3db | ||
|
|
e901a1f231 | ||
|
|
e4c47aca9e | ||
|
|
a43a3db098 | ||
|
|
d651606800 | ||
|
|
5501ab9083 | ||
|
|
660a21deb1 | ||
|
|
be7949b909 | ||
|
|
a688656f6d | ||
|
|
62365529cc | ||
|
|
dcea869098 | ||
|
|
634a8702cd | ||
|
|
fee993c309 | ||
|
|
bf76707e92 | ||
|
|
da847f6567 | ||
|
|
7a5d25f2e3 | ||
|
|
bf0dedd447 | ||
|
|
3f7dcc6acf | ||
|
|
2efe8b4186 | ||
|
|
068f5771b2 | ||
|
|
c2b1be288e | ||
|
|
163ad248af | ||
|
|
4598c3d852 | ||
|
|
a1dec131c7 | ||
|
|
133585f46a | ||
|
|
3ea81ce2fb | ||
|
|
590fe211c4 | ||
|
|
78cda03d61 | ||
|
|
e126cbf644 | ||
|
|
cc12ae7712 | ||
|
|
e8486abccf | ||
|
|
15f074a45b | ||
|
|
a426d98e92 | ||
|
|
45d171e0e3 | ||
|
|
5950c33a43 | ||
|
|
ea1b584436 | ||
|
|
a24ede364d | ||
|
|
c6fe456cac | ||
|
|
4008fb3a53 | ||
|
|
96588089ef | ||
|
|
e4c96dc6d8 | ||
|
|
c205e0da1b | ||
|
|
7b61605834 | ||
|
|
e7fb05d7e0 | ||
|
|
8f0680f5fc | ||
|
|
9c03dd001c | ||
|
|
30407f5b4e | ||
|
|
3a5378d201 | ||
|
|
d4f3577f5e | ||
|
|
408e9bf3fc | ||
|
|
f5dd91afe5 | ||
|
|
8922459418 | ||
|
|
caeadbc41e | ||
|
|
99143c0e3b | ||
|
|
1b145e38a3 | ||
|
|
f59cce15c0 | ||
|
|
7655c251a2 | ||
|
|
5608cb542f | ||
|
|
62add53c08 | ||
|
|
43bae6c05b | ||
|
|
55777d33ad | ||
|
|
0f5d14b589 | ||
|
|
00703d1570 | ||
|
|
fd03c33f4d | ||
|
|
c20f91b6d8 | ||
|
|
10b22e9e42 | ||
|
|
329f0871d5 | ||
|
|
66996f491c | ||
|
|
9ae39f3900 | ||
|
|
9d0db3c1e5 | ||
|
|
5932dd99ad | ||
|
|
910f0083cd | ||
|
|
32a8676572 | ||
|
|
801829ccbf | ||
|
|
b5107d21dd | ||
|
|
158bf873bd | ||
|
|
40cfb9876d | ||
|
|
12e3214f70 | ||
|
|
0eb68ec461 | ||
|
|
f231565163 | ||
|
|
7cca53bcc5 | ||
|
|
be94c94309 | ||
|
|
8e2d654b40 | ||
|
|
2ed5c0c5cc | ||
|
|
745ad3b9e9 | ||
|
|
3ce114760f | ||
|
|
bae7d1fc1d | ||
|
|
e4d9dfc128 | ||
|
|
7490ba3179 | ||
|
|
45da12ad55 | ||
|
|
75f99bf899 | ||
|
|
bd2b1cc166 | ||
|
|
5fa8d64994 | ||
|
|
034957b556 | ||
|
|
a9d7c73b04 | ||
|
|
b09fe05fe6 | ||
|
|
ed146549ef | ||
|
|
86c11db1a1 | ||
|
|
580cd57433 | ||
|
|
9854ce82bd | ||
|
|
af11df97f5 | ||
|
|
9b4e664908 | ||
|
|
567e1ee116 | ||
|
|
ef7fa5363a | ||
|
|
f8bd7c2e64 | ||
|
|
de46c9ee36 | ||
|
|
5969e2d7ed | ||
|
|
83047558d5 | ||
|
|
f239868299 | ||
|
|
e4b962a3a6 | ||
|
|
b68a94c0e5 | ||
|
|
ec53ca8423 | ||
|
|
1ba0729e34 | ||
|
|
73425c0052 | ||
|
|
679859fb37 | ||
|
|
dbdc660464 | ||
|
|
aa22e7e952 | ||
|
|
b920e7e95c | ||
|
|
d14b23ca82 | ||
|
|
4e8f69f692 | ||
|
|
c96cf2b0e5 | ||
|
|
4921cfb593 | ||
|
|
395545f7b1 | ||
|
|
f9d336a3a6 | ||
|
|
b32603b472 | ||
|
|
1124c48c8d | ||
|
|
98e429505c | ||
|
|
d0b616ba24 | ||
|
|
dac4ffcb98 | ||
|
|
680310cf70 | ||
|
|
67ff82810f | ||
|
|
87e71ea860 | ||
|
|
26c110291e | ||
|
|
9879f074b4 | ||
|
|
65168c71c0 | ||
|
|
4c4996ee2a | ||
|
|
e0c67f87b0 | ||
|
|
352c8ee867 | ||
|
|
fe5cc1f8f3 | ||
|
|
eec4be1845 | ||
|
|
2f86b5c7b0 | ||
|
|
0d672c4f99 | ||
|
|
ac3fdbc2cd | ||
|
|
0a7ad44d23 | ||
|
|
18a86d3f12 | ||
|
|
665e66a9a6 | ||
|
|
06dc4117c7 | ||
|
|
2651afcef0 | ||
|
|
ce4d828380 | ||
|
|
74fba486bd | ||
|
|
56075cb7d9 | ||
|
|
d71bc775d5 | ||
|
|
45c5801538 | ||
|
|
cf41b524b0 | ||
|
|
e2a3e55a17 | ||
|
|
ae35bd2047 | ||
|
|
2f0ca6f7c0 | ||
|
|
37428c01dd | ||
|
|
4116d95a3e | ||
|
|
35ae2b783f | ||
|
|
8a24a6d192 | ||
|
|
19374a5df4 | ||
|
|
12da6fbd18 | ||
|
|
573ff15925 | ||
|
|
1b2abbe321 | ||
|
|
4a03da6b96 | ||
|
|
cf3998942f | ||
|
|
0c71f783fc | ||
|
|
d30b30b24f | ||
|
|
7823ec3fc8 | ||
|
|
1e5883f028 | ||
|
|
33c3cf4c4f | ||
|
|
f41ace4d7c | ||
|
|
65d2d45a82 | ||
|
|
47ca483459 | ||
|
|
ee759af078 | ||
|
|
872037cf4d | ||
|
|
6aaa083157 | ||
|
|
6a88524f8e | ||
|
|
82d93d2602 | ||
|
|
d62037ef6a | ||
|
|
7314b5a339 | ||
|
|
62bc230521 | ||
|
|
3e0d34d148 | ||
|
|
aff1cc1cc3 | ||
|
|
6ddc7fa4cc | ||
|
|
957db1ec11 | ||
|
|
ae806da3f1 | ||
|
|
6a03c3e77d | ||
|
|
72b18eadf3 | ||
|
|
67aa583709 | ||
|
|
21f3755e44 | ||
|
|
c9b6df846e | ||
|
|
7e23a8169f | ||
|
|
b139eadf0b | ||
|
|
71ad648331 | ||
|
|
b8c7752356 | ||
|
|
b157f2085f | ||
|
|
2fda7b8011 | ||
|
|
5b24d19630 | ||
|
|
76652f6c6b | ||
|
|
724ae51110 | ||
|
|
1503124108 | ||
|
|
007125a071 | ||
|
|
b5f5b0b4aa | ||
|
|
cbda59e547 | ||
|
|
a885e16049 | ||
|
|
07eabad18d | ||
|
|
cf079a159f | ||
|
|
93176989fd | ||
|
|
7a56141894 | ||
|
|
31cc0ff6e9 | ||
|
|
8719b3eb64 | ||
|
|
5347624455 | ||
|
|
25210339d9 | ||
|
|
72c7cd2536 | ||
|
|
d018eeb376 | ||
|
|
d1424276bc | ||
|
|
cf9696a8cf | ||
|
|
a7cbe526e3 | ||
|
|
fe1c58ad27 | ||
|
|
753d01d413 | ||
|
|
feacb3ed14 | ||
|
|
f5b1e6d03a | ||
|
|
46fc2dd8d0 | ||
|
|
b063aae130 | ||
|
|
655a729143 | ||
|
|
5d2138b95e | ||
|
|
0b24cc29c1 | ||
|
|
2fa7b532b1 | ||
|
|
aa1ed52f64 | ||
|
|
29dddd7d62 | ||
|
|
6ddbea316a | ||
|
|
8da80f0710 | ||
|
|
24382b8607 | ||
|
|
65438e837d | ||
|
|
1a3cb8b623 | ||
|
|
6bf4a0d09d | ||
|
|
9ae734672b | ||
|
|
cfa84476c8 | ||
|
|
5b0d160df4 | ||
|
|
41f858eb04 | ||
|
|
c5753b898a | ||
|
|
c6810409c7 | ||
|
|
f6c16ec53d | ||
|
|
84a6ee8cbf | ||
|
|
e651a13980 | ||
|
|
f494570725 | ||
|
|
5955ca74d2 | ||
|
|
eb4fa8620d | ||
|
|
34fe7dd6d1 | ||
|
|
4cbb3cb43c | ||
|
|
050acdf580 | ||
|
|
358da4051e | ||
|
|
ffb51c1515 | ||
|
|
72d4952812 | ||
|
|
04bf86c21d | ||
|
|
d392dc82a1 | ||
|
|
f7f4289614 | ||
|
|
72f9951cb1 | ||
|
|
8450f56093 | ||
|
|
cb2a25ad46 | ||
|
|
a028172cf6 | ||
|
|
91aa48ac9a | ||
|
|
218320749f | ||
|
|
6a1ff56e7b | ||
|
|
8f7c4951b8 | ||
|
|
6215a7d65e | ||
|
|
0e28b1ffe1 | ||
|
|
7dd435d677 | ||
|
|
7fd5209cdb | ||
|
|
ed5b6962d7 | ||
|
|
98718c0693 | ||
|
|
f02588855a | ||
|
|
c63841fdee | ||
|
|
b49b4601cd | ||
|
|
ed352d2d21 | ||
|
|
dab56121a2 | ||
|
|
df02afaed9 | ||
|
|
e6fe121c1e | ||
|
|
b37fbc1284 | ||
|
|
f5ac554022 | ||
|
|
df76c82449 | ||
|
|
8f1f978800 | ||
|
|
bf3cc6690c | ||
|
|
20ba7eaaf3 | ||
|
|
95708cd5ab | ||
|
|
fba58bba71 | ||
|
|
bc6ff3e3bc | ||
|
|
3415be4c56 | ||
|
|
05d6f5d806 | ||
|
|
ed8266bb43 | ||
|
|
759f35ff9c | ||
|
|
bcac8f1599 | ||
|
|
0abf054825 | ||
|
|
825b76e28e | ||
|
|
0a13ac142e | ||
|
|
566e6634a6 | ||
|
|
4b888e6911 | ||
|
|
0cfc89f574 | ||
|
|
337382b7e6 | ||
|
|
4ed657c930 | ||
|
|
5895b37965 | ||
|
|
6979b8e11e | ||
|
|
20713cf158 | ||
|
|
13ff0846b1 | ||
|
|
e92d091cb3 | ||
|
|
6b1a435cdc | ||
|
|
3f04b465f3 | ||
|
|
3f5c8fe2cb | ||
|
|
d1cf6c68f3 | ||
|
|
7117f00480 | ||
|
|
d4f37343a2 | ||
|
|
71e15e9cab | ||
|
|
eadf00feba | ||
|
|
95f28fad2b | ||
|
|
9753137a72 | ||
|
|
e4f7436dfb | ||
|
|
d39211310d | ||
|
|
0a6fb3ec0a | ||
|
|
5232cf7cec | ||
|
|
6e16ffe05f | ||
|
|
2d6895aeea | ||
|
|
b5311e1448 | ||
|
|
cc63eb383d | ||
|
|
01736ca685 | ||
|
|
be47bb7263 | ||
|
|
bcb7d88ed7 | ||
|
|
cf58c1b4b5 | ||
|
|
70c57928e7 | ||
|
|
c8219b29c0 | ||
|
|
15a9f80430 | ||
|
|
0684dfe869 | ||
|
|
83a89566ac | ||
|
|
04f486b003 | ||
|
|
f135c92434 | ||
|
|
78b095d01a | ||
|
|
1b8bd494e2 | ||
|
|
481925ac78 | ||
|
|
4854b2b1c0 | ||
|
|
2d7b33459e | ||
|
|
27e0c7421b | ||
|
|
b26c3d050c | ||
|
|
439370e25a | ||
|
|
1be4f6e20c | ||
|
|
2714c7cce9 | ||
|
|
952935de23 | ||
|
|
bdb8b5ea39 | ||
|
|
3ad4e28a2c | ||
|
|
48d0d068d1 | ||
|
|
56e166d61a | ||
|
|
672d753adf | ||
|
|
0d9ba92db4 | ||
|
|
8cf25d3602 | ||
|
|
a6bc44dc10 | ||
|
|
408d66ee74 | ||
|
|
b136bb74b8 | ||
|
|
18b2b6f447 | ||
|
|
490d1775a2 | ||
|
|
458de2d2e0 | ||
|
|
51ae3fc62f | ||
|
|
58c5c55d09 | ||
|
|
4c2bcb9e6b | ||
|
|
498379bb7e | ||
|
|
e7f3b115a4 | ||
|
|
0ebfe85d8e | ||
|
|
8e29a990cb | ||
|
|
a960ccd786 | ||
|
|
6b86e836d7 | ||
|
|
fb35b9b10a | ||
|
|
a45773e1ca | ||
|
|
2405a6f21e | ||
|
|
533dd6135e | ||
|
|
efc25543ca | ||
|
|
82d4745da3 | ||
|
|
ac6e95c442 | ||
|
|
375f23ac9e | ||
|
|
8e5a01d82c | ||
|
|
910658aa93 | ||
|
|
d766ffa040 | ||
|
|
b960640e03 | ||
|
|
c984617b1c | ||
|
|
a12a7127c0 | ||
|
|
98a6a5c93d | ||
|
|
27202fd740 | ||
|
|
c01d02de27 | ||
|
|
1d23bcc979 | ||
|
|
ac8abdaa17 | ||
|
|
613977c6f9 | ||
|
|
54159c9d05 | ||
|
|
8d5d477b4a | ||
|
|
2c73906ad3 | ||
|
|
079fb34120 | ||
|
|
17ed1cdc00 | ||
|
|
d53ea584ba | ||
|
|
b435256911 | ||
|
|
27e996dba0 | ||
|
|
22f3bd1073 | ||
|
|
fb564fa817 | ||
|
|
be9db2930f | ||
|
|
5bce95a686 | ||
|
|
f6ca9b9d0f | ||
|
|
88f186907b | ||
|
|
9faf1d9de5 | ||
|
|
8b1d1d0f6d | ||
|
|
8c19e2c3f2 | ||
|
|
d2d8ee504d | ||
|
|
d96b279beb | ||
|
|
f5e7f9249c | ||
|
|
56c33ee82b | ||
|
|
b05dd4cc2c | ||
|
|
36d4ce8718 | ||
|
|
ddec7ab643 | ||
|
|
75201c9b30 | ||
|
|
99c81e5a5d | ||
|
|
b84ad39133 | ||
|
|
4a19e2b673 | ||
|
|
475c3559f6 | ||
|
|
58246f72dd | ||
|
|
b90ce2a2af | ||
|
|
4a0fc5ca0e | ||
|
|
c29d902b8e | ||
|
|
ab629c2048 | ||
|
|
e970ca49e8 | ||
|
|
99e78092ed | ||
|
|
4af91b5ab6 | ||
|
|
539121070a | ||
|
|
2a1bd92e1a | ||
|
|
2c1ebc0439 | ||
|
|
2d605f5dfb | ||
|
|
0cd09cf03a | ||
|
|
3ad1e8a3ba | ||
|
|
230722945e | ||
|
|
a429dcf978 | ||
|
|
0131031ac4 | ||
|
|
a418fc810a | ||
|
|
e71adbd26d | ||
|
|
8a525aee8a | ||
|
|
463b0fa28a | ||
|
|
007ebadf16 | ||
|
|
c7af81bf0c | ||
|
|
749508871b | ||
|
|
d112e0ea42 | ||
|
|
54f8771a9c | ||
|
|
0a3c83288e | ||
|
|
52a866147e | ||
|
|
6629eaf485 | ||
|
|
74239521cd | ||
|
|
8ae95c4e30 | ||
|
|
c31e191d7e | ||
|
|
c3134f779d | ||
|
|
d4749c139b | ||
|
|
63d6c32063 | ||
|
|
08299902e3 | ||
|
|
db04d6e642 | ||
|
|
6ddbd77009 | ||
|
|
4a4779fc63 | ||
|
|
46bb8d2cb5 | ||
|
|
31b2eeb293 | ||
|
|
8e9becd579 | ||
|
|
d067de086d | ||
|
|
8c6d395d89 | ||
|
|
f66b26a866 | ||
|
|
83f00d69ce | ||
|
|
8b2923b56d | ||
|
|
46af313c25 | ||
|
|
85dda759ec | ||
|
|
27fb44277f | ||
|
|
ea1aafbab2 | ||
|
|
2c446f939e | ||
|
|
47e427a851 | ||
|
|
95b8efae20 | ||
|
|
53774735d4 | ||
|
|
36c6c5a35e | ||
|
|
0beb07c87e | ||
|
|
64fd8e3be9 | ||
|
|
45c516ea3f | ||
|
|
26667c0a59 | ||
|
|
61e0379eb3 | ||
|
|
759df9bdd5 | ||
|
|
33e7ca08d8 | ||
|
|
c3d0d8bf63 | ||
|
|
8387f1e204 | ||
|
|
fe778293c1 | ||
|
|
991afb7722 | ||
|
|
a176542114 | ||
|
|
a3f555e816 | ||
|
|
dae5453e13 | ||
|
|
082826287e | ||
|
|
7418691cf3 | ||
|
|
29e72de64b | ||
|
|
4955b4a4a8 | ||
|
|
7c76595314 | ||
|
|
1495003103 | ||
|
|
fc1b74d48f | ||
|
|
830d0e9e7a | ||
|
|
7310c06162 | ||
|
|
045ccaa219 | ||
|
|
9d6a276342 | ||
|
|
9204d25b62 | ||
|
|
6c847292c7 | ||
|
|
b2712119d1 | ||
|
|
7728046309 | ||
|
|
17e18a2a7a | ||
|
|
ce8bedb340 | ||
|
|
14dc42e148 | ||
|
|
442c2294e9 | ||
|
|
5334514d55 | ||
|
|
0d5b431e6a | ||
|
|
8b10ee0028 | ||
|
|
9682abdded | ||
|
|
62cef0d141 | ||
|
|
548132ce7e | ||
|
|
4c19450f04 | ||
|
|
b4ee44ca00 | ||
|
|
f2db2ae474 | ||
|
|
f211b3a4da | ||
|
|
936f65ecc9 | ||
|
|
83985965f2 | ||
|
|
7bc38a35e8 | ||
|
|
9879f7fa0a | ||
|
|
7c856d08c4 | ||
|
|
6c554b4d51 | ||
|
|
187bc83db9 | ||
|
|
9a08379a5b | ||
|
|
e807d61bd7 | ||
|
|
f3724d6314 | ||
|
|
be8e393c13 | ||
|
|
0357f1461e | ||
|
|
8f99d80ac1 | ||
|
|
2c05b3b89f | ||
|
|
07bec16539 | ||
|
|
9938fdd4a2 | ||
|
|
320d2c5c96 | ||
|
|
6860cca9bb | ||
|
|
8e98eb439c | ||
|
|
29c4b8e6ee | ||
|
|
d8559a81f4 | ||
|
|
8322e49305 | ||
|
|
c0b6bf9f89 | ||
|
|
620d421a4b | ||
|
|
071ec61683 | ||
|
|
c0a532a0fe | ||
|
|
feb484dc9f | ||
|
|
4b819bbcc4 | ||
|
|
0257c5b30d | ||
|
|
3b1bb02e41 | ||
|
|
229dc03b23 | ||
|
|
2e883c2050 | ||
|
|
65abb1dfaa | ||
|
|
c72b551e80 | ||
|
|
9718e5aace | ||
|
|
db6ceea711 | ||
|
|
4a0e3227fc | ||
|
|
428e35237f | ||
|
|
74972336c6 | ||
|
|
fc1b825f46 | ||
|
|
8f1a8e3ce9 | ||
|
|
4efec0b266 | ||
|
|
9c0c819dce | ||
|
|
bb37372be0 | ||
|
|
fe8240c47a | ||
|
|
0b5def0f39 | ||
|
|
b60be97b1f | ||
|
|
23274ef914 | ||
|
|
713fdaeac1 | ||
|
|
2a44937d09 | ||
|
|
4751cc04fc | ||
|
|
5daeb0ee95 | ||
|
|
1bcfd57193 | ||
|
|
dbd697a473 | ||
|
|
24722d3644 | ||
|
|
a7f6205030 | ||
|
|
2ce4f31a41 | ||
|
|
22ac095dce | ||
|
|
0cba25fc0c | ||
|
|
ee883571da | ||
|
|
c7e81ed69f | ||
|
|
4b44beb5b1 | ||
|
|
d624a4bac9 | ||
|
|
fb90a668a9 | ||
|
|
52fb17f3f0 | ||
|
|
4419fd5301 | ||
|
|
1ee11ea6f7 | ||
|
|
ca5d69016f | ||
|
|
a077944629 | ||
|
|
379404077a | ||
|
|
5c6ff45cc9 | ||
|
|
395342be37 | ||
|
|
c26960380a | ||
|
|
42434229ce | ||
|
|
1d902c64d7 | ||
|
|
0a3857b1c2 | ||
|
|
d538b809b1 | ||
|
|
185e234ef2 | ||
|
|
aac4aafde0 | ||
|
|
873ebee256 | ||
|
|
3807f185d6 | ||
|
|
d729f93b17 | ||
|
|
16058c5efb | ||
|
|
18ff80875a | ||
|
|
0cdba2a13d | ||
|
|
5ff9131910 | ||
|
|
a4a7d2180c | ||
|
|
fe422a101a | ||
|
|
6cec61dc42 | ||
|
|
d4f52bce2b | ||
|
|
2262e1c4c2 | ||
|
|
a7ba21f2f9 | ||
|
|
043a4122b4 | ||
|
|
e5f3be9669 | ||
|
|
34cb04cbde | ||
|
|
610789fd6d | ||
|
|
aa880264e3 | ||
|
|
715b028f47 | ||
|
|
9f7ad7588e | ||
|
|
fb77747a1b | ||
|
|
7b9c00cd77 | ||
|
|
b7448a7422 | ||
|
|
e067d9b6df | ||
|
|
3c20669296 | ||
|
|
8d33d8f216 | ||
|
|
c27d427799 | ||
|
|
90a6855e39 | ||
|
|
4dfba13aea | ||
|
|
1c4efb91b9 | ||
|
|
8a47e99a2c | ||
|
|
1ca4cceec8 | ||
|
|
a42c7a5198 | ||
|
|
e3b44ac7d1 | ||
|
|
1e17c2212a | ||
|
|
fd09833df1 | ||
|
|
8579b7130a | ||
|
|
ca4a00196a | ||
|
|
189c56342a | ||
|
|
2c05c9595b | ||
|
|
83bcd39791 | ||
|
|
db36f618db | ||
|
|
c03b5d6c00 | ||
|
|
ee867df0be | ||
|
|
2180fb6728 | ||
|
|
de244efbf9 | ||
|
|
d6a66d1eb0 | ||
|
|
b4a80751b2 | ||
|
|
748698b33f |
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch.
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
[](https://crowdin.com/project/bitwarden-mobile)
|
||||
|
||||
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||
|
||||
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
|
||||
|
||||
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/mail/compose/kspearrin).
|
||||
|
||||
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||
5
ISSUE_TEMPLATE.md
Normal file
@@ -0,0 +1,5 @@
|
||||
<!--
|
||||
Please do not submit feature requests. The [Community Forums][1] has a
|
||||
section for submitting, voting for, and discussing product feature requests.
|
||||
[1]: https://community.bitwarden.com
|
||||
-->
|
||||
674
LICENSE.txt
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
{one line to give the program's name and a brief idea of what it does.}
|
||||
Copyright (C) {year} {name of author}
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
{project} Copyright (C) {year} {fullname}
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
||||
31
README.md
@@ -1,13 +1,34 @@
|
||||
# bitwarden mobile
|
||||
[](https://ci.appveyor.com/project/bitwarden/mobile)
|
||||
[](https://crowdin.com/project/bitwarden-mobile)
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
<a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://linkmaker.itunes.apple.com/images/badges/en-us/badge_appstore-lrg.svg" width="165" height="40"></a>
|
||||
# Bitwarden Mobile Application
|
||||
|
||||
Android on the Google Play store coming soon!
|
||||
<a href="https://play.google.com/store/apps/details?id=com.x8bit.bitwarden" target="_blank"><img alt="Get it on Google Play" src="https://imgur.com/YQzmZi9.png" width="153" height="46"></a> <a href="https://itunes.apple.com/us/app/bitwarden-free-password-manager/id1137397744?mt=8" target="_blank"><img src="https://imgur.com/GdGqPMY.png" width="135" height="40"></a>
|
||||
|
||||
The bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
|
||||
The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, UWP, and Xamarin Forms.
|
||||
|
||||
<img src="https://i.imgur.com/R7H2tkQ.png" alt="" width="300" height="533" /> <img src="https://i.imgur.com/3BO1Wcg.png" alt="" width="300" height="533" />
|
||||
|
||||
# Build/Run
|
||||
|
||||
**Requirements**
|
||||
|
||||
- [Visual Studio](https://store.xamarin.com/)
|
||||
|
||||
**API endpoint**
|
||||
|
||||
By default the app is targeting the production API. If you are running the [Core](https://github.com/bitwarden/core) API locally,
|
||||
you'll need to switch the app to target your local instance. Open `src/App/Utilities/ApiHttpClient.cs` and `src/App/Utilities/IdentityHttpClient.cs` and set the `BaseAddress` to your local
|
||||
API endpoints (ex. `new Uri("http://localhost:5000")`). Alternatively, you can also adjust the environment endpoints from the environment settings page on the home screen of the app (log out).
|
||||
|
||||
**Run the app**
|
||||
|
||||
After restoring the nuget packages, you can now build and run the app.
|
||||
|
||||
# Contribute
|
||||
|
||||
Code contributions are welcome! Visual Studio or Xamarin Studio is required to work on this project. Please commit any pull requests against the `master` branch.
|
||||
Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
|
||||
|
||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature.
|
||||
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||
|
||||
45
SECURITY.md
Normal file
@@ -0,0 +1,45 @@
|
||||
Bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
||||
users safe. If you believe you've found a security issue in our product or service, we encourage you to
|
||||
notify us. We welcome working with you to resolve the issue promptly. Thanks in advance!
|
||||
|
||||
# Disclosure Policy
|
||||
|
||||
- Let us know as soon as possible upon discovery of a potential security issue, and we'll make every
|
||||
effort to quickly resolve the issue.
|
||||
- Provide us a reasonable amount of time to resolve the issue before any disclosure to the public or a
|
||||
third-party. We may publicly disclose the issue before resolving it, if appropriate.
|
||||
- Make a good faith effort to avoid privacy violations, destruction of data, and interruption or
|
||||
degradation of our service. Only interact with accounts you own or with explicit permission of the
|
||||
account holder.
|
||||
- If you would like to encrypt your report, please use the PGP key with long ID
|
||||
`0xDE6887086F892325FEC04CC0D847525B6931381F` (available in the public keyserver pool).
|
||||
|
||||
# In-scope
|
||||
|
||||
- Security issues in any current release of Bitwarden. This includes the web vault, browser extension,
|
||||
and mobile apps (iOS and Android). Product downloads are available at https://bitwarden.com. Source
|
||||
code is available at https://github.com/bitwarden.
|
||||
|
||||
# Exclusions
|
||||
|
||||
The following bug classes are out-of scope:
|
||||
|
||||
- Bugs that are already reported on any of Bitwarden's issue trackers (https://github.com/bitwarden),
|
||||
or that we already know of. Note that some of our issue tracking is private.
|
||||
- Issues in an upstream software dependency (ex: Xamarin, ASP.NET) which are already reported to the
|
||||
upstream maintainer.
|
||||
- Attacks requiring physical access to a user's device.
|
||||
- Self-XSS
|
||||
- Issues related to software or protocols not under Bitwarden's control
|
||||
- Vulnerabilities in outdated versions of Bitwarden
|
||||
- Missing security best practices that do not directly lead to a vulnerability
|
||||
- Issues that do not have any impact on the general public
|
||||
|
||||
While researching, we'd like to ask you to refrain from:
|
||||
|
||||
- Denial of service
|
||||
- Spamming
|
||||
- Social engineering (including phishing) of Bitwarden staff or contractors
|
||||
- Any physical attempts against Bitwarden property or data centers
|
||||
|
||||
Thank you for helping keep Bitwarden and our users safe!
|
||||
24
appveyor.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
install:
|
||||
- choco install cloc --no-progress
|
||||
- "cloc --vcs git --exclude-dir Resources,store,test,UWP,Properties --include-lang C#,JavaScript,TypeScript,PowerShell"
|
||||
# - appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||
# - appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
|
||||
# - vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
|
||||
before_build:
|
||||
- nuget restore
|
||||
- IF DEFINED keystore_dec_secret nuget install secure-file -ExcludeVersion
|
||||
- IF DEFINED google_services_dec_secret secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc -secret %google_services_dec_secret%
|
||||
after_build:
|
||||
- ps: IF($env:keystore_dec_secret) { .\src\Android\ci-build-apks.ps1 }
|
||||
on_success:
|
||||
- IF DEFINED play_dec_secret secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret %play_dec_secret%
|
||||
- IF DEFINED play_dec_secret dotnet store\google\Publisher\bin\Release\netcoreapp2.0\Publisher.dll %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk alpha
|
||||
artifacts:
|
||||
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
||||
- path: com.x8bit.bitwarden-fdroid-%APPVEYOR_BUILD_NUMBER%.apk
|
||||
branches:
|
||||
except:
|
||||
- l10n_master
|
||||
skip_tags: true
|
||||
configuration: Release
|
||||
image: Visual Studio 2017
|
||||
@@ -1,14 +1,12 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 14
|
||||
VisualStudioVersion = 14.0.25420.1
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2010
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{1F78403F-9A28-405B-9289-B9DBEB55F074}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App", "src\App\App.csproj", "{B490C5DA-639E-4994-ABD2-54222B8A348E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC730FD9-F623-4B6C-B503-95CDCFBCF277}"
|
||||
@@ -19,7 +17,22 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Ex
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{B2538ADA-B605-4D6F-ACD2-62A409680F84}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Test", "test\iOS.Test\iOS.Test.csproj", "{6702027A-F726-4149-863E-7CB924674B9A}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "store", "store", "{92470CBD-9047-4C3C-8EA3-D972D6622D84}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E399654-26A2-46F6-B9CA-1B496A3F370A}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{8A279EE4-4537-4656-9C93-44945E594556}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{D5D91152-CB01-4F24-A503-304D3A94408B}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F0E2E596-C3DB-474A-9C88-7824662894FA}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
appveyor.yml = appveyor.yml
|
||||
crowdin.yml = crowdin.yml
|
||||
README.md = README.md
|
||||
SECURITY.md = SECURITY.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -41,6 +54,12 @@ Global
|
||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
FDroid|Any CPU = FDroid|Any CPU
|
||||
FDroid|ARM = FDroid|ARM
|
||||
FDroid|iPhone = FDroid|iPhone
|
||||
FDroid|iPhoneSimulator = FDroid|iPhoneSimulator
|
||||
FDroid|x64 = FDroid|x64
|
||||
FDroid|x86 = FDroid|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM = Release|ARM
|
||||
Release|iPhone = Release|iPhone
|
||||
@@ -86,11 +105,27 @@ Global
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x64.Deploy.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|x86.Deploy.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|Any CPU.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|ARM.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhone.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|iPhoneSimulator.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x64.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.ActiveCfg = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.Build.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.FDroid|x86.Deploy.0 = FDroid|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
@@ -122,19 +157,27 @@ Global
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|Any CPU.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|ARM.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x64.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|Any CPU.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|ARM.ActiveCfg = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|ARM.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhone.Build.0 = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x64.ActiveCfg = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x64.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x86.ActiveCfg = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.FDroid|x86.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|Any CPU.Build.0 = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|ARM.ActiveCfg = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhone.Build.0 = Release|iPhone
|
||||
@@ -142,54 +185,6 @@ Global
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x64.ActiveCfg = Release|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|x86.ActiveCfg = Release|iPhone
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x64.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.AppStore|x86.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||
@@ -217,9 +212,20 @@ Global
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|ARM.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhone.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x64.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x64.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x86.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.FDroid|x86.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
@@ -247,19 +253,27 @@ Global
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|Any CPU.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|ARM.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x64.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|Any CPU.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|Any CPU.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|ARM.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|ARM.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhone.ActiveCfg = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhone.Build.0 = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x64.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x64.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x86.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.FDroid|x86.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|Any CPU.Build.0 = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|ARM.ActiveCfg = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhone.Build.0 = Release|iPhone
|
||||
@@ -292,21 +306,28 @@ Global
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x86.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|ARM.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|ARM.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhone.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x64.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x64.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x86.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.FDroid|x86.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
@@ -315,38 +336,126 @@ Global
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|x64.ActiveCfg = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.AppStore|x86.ActiveCfg = AppStore|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|ARM.ActiveCfg = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|x64.ActiveCfg = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Debug|x86.ActiveCfg = Debug|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|ARM.ActiveCfg = Release|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhone.Build.0 = Release|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|x64.ActiveCfg = Release|iPhone
|
||||
{6702027A-F726-4149-863E-7CB924674B9A}.Release|x86.ActiveCfg = Release|iPhone
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|ARM.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|ARM.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x64.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x64.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x86.ActiveCfg = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.FDroid|x86.Build.0 = FDroid|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8A279EE4-4537-4656-9C93-44945E594556}.Release|x86.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|ARM.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|ARM.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x64.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x64.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|ARM.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|ARM.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x64.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x64.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x86.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.AppStore|x86.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|Any CPU.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|ARM.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|ARM.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhone.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhone.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x64.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x64.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x86.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.FDroid|x86.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -354,10 +463,14 @@ Global
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{B490C5DA-639E-4994-ABD2-54222B8A348E} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{6702027A-F726-4149-863E-7CB924674B9A} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
|
||||
{2E399654-26A2-46F6-B9CA-1B496A3F370A} = {92470CBD-9047-4C3C-8EA3-D972D6622D84}
|
||||
{8A279EE4-4537-4656-9C93-44945E594556} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {318CB2DF-0118-43A3-AC83-56BADCF71CCD}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
28
crowdin.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
files:
|
||||
- source: /src/App/Resources/AppResources.resx
|
||||
translation: /src/App/Resources/AppResources.%two_letters_code%.resx
|
||||
update_option: update_as_unapproved
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh-Hans
|
||||
zh-TW: zh-Hant
|
||||
pt-PT: pt-PT
|
||||
pt-BR: pt-BR
|
||||
- source: /store/apple/en/copy.resx
|
||||
translation: /store/apple/%two_letters_code%/copy.resx
|
||||
update_option: update_as_unapproved
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh-Hans
|
||||
zh-TW: zh-Hant
|
||||
pt-PT: pt-PT
|
||||
pt-BR: pt-BR
|
||||
- source: /store/google/en/copy.resx
|
||||
translation: /store/google/%two_letters_code%/copy.resx
|
||||
update_option: update_as_unapproved
|
||||
languages_mapping:
|
||||
two_letters_code:
|
||||
zh-CN: zh-Hans
|
||||
zh-TW: zh-Hant
|
||||
pt-BR: pt-BR
|
||||
pt-PT: pt-PT
|
||||
|
Before Width: | Height: | Size: 255 KiB |
|
Before Width: | Height: | Size: 261 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 242 KiB |
|
Before Width: | Height: | Size: 228 KiB |
|
Before Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 322 KiB |
|
Before Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 657 KiB |
|
Before Width: | Height: | Size: 642 KiB |
|
Before Width: | Height: | Size: 581 KiB |
BIN
src/Android/8bit.keystore.enc
Normal file
146
src/Android/Autofill/AutofillHelpers.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Android.Content;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Widget;
|
||||
using System.Linq;
|
||||
using Android.App;
|
||||
using Bit.App.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Enums;
|
||||
using Android.Views.Autofill;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public static class AutofillHelpers
|
||||
{
|
||||
private static int _pendingIntentId = 0;
|
||||
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService service)
|
||||
{
|
||||
var items = new List<FilledItem>();
|
||||
|
||||
if(parser.FieldCollection.FillableForLogin)
|
||||
{
|
||||
var ciphers = await service.GetAllAsync(parser.Uri);
|
||||
if(ciphers.Item1.Any() || ciphers.Item2.Any())
|
||||
{
|
||||
var allCiphers = ciphers.Item1.ToList();
|
||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||
foreach(var cipher in allCiphers)
|
||||
{
|
||||
items.Add(new FilledItem(cipher));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(parser.FieldCollection.FillableForCard)
|
||||
{
|
||||
var ciphers = await service.GetAllAsync();
|
||||
foreach(var cipher in ciphers.Where(c => c.Type == CipherType.Card))
|
||||
{
|
||||
items.Add(new FilledItem(cipher));
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
public static FillResponse BuildFillResponse(Context context, Parser parser, List<FilledItem> items, bool locked)
|
||||
{
|
||||
var responseBuilder = new FillResponse.Builder();
|
||||
if(items != null && items.Count > 0)
|
||||
{
|
||||
foreach(var item in items)
|
||||
{
|
||||
var dataset = BuildDataset(context, parser.FieldCollection, item);
|
||||
if(dataset != null)
|
||||
{
|
||||
responseBuilder.AddDataset(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
responseBuilder.AddDataset(BuildVaultDataset(context, parser.FieldCollection, parser.Uri, locked));
|
||||
AddSaveInfo(responseBuilder, parser.FieldCollection);
|
||||
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
||||
return responseBuilder.Build();
|
||||
}
|
||||
|
||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
|
||||
{
|
||||
var datasetBuilder = new Dataset.Builder(
|
||||
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon));
|
||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
{
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked)
|
||||
{
|
||||
var intent = new Intent(context, typeof(MainActivity));
|
||||
intent.PutExtra("autofillFramework", true);
|
||||
if(fields.FillableForLogin)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
|
||||
}
|
||||
else if(fields.FillableForCard)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
|
||||
}
|
||||
else if(fields.FillableForIdentity)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
intent.PutExtra("autofillFrameworkUri", uri);
|
||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||
PendingIntentFlags.CancelCurrent);
|
||||
|
||||
var view = BuildListView(context.PackageName, AppResources.AutofillWithBitwarden,
|
||||
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault, Resource.Drawable.icon);
|
||||
|
||||
var datasetBuilder = new Dataset.Builder(view);
|
||||
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
|
||||
|
||||
// Dataset must have a value set. We will reset this in the main activity when the real item is chosen.
|
||||
foreach(var autofillId in fields.AutofillIds)
|
||||
{
|
||||
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
||||
}
|
||||
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
|
||||
public static RemoteViews BuildListView(string packageName, string text, string subtext, int iconId)
|
||||
{
|
||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
||||
view.SetTextViewText(Resource.Id.text, text);
|
||||
view.SetTextViewText(Resource.Id.text2, subtext);
|
||||
view.SetImageViewResource(Resource.Id.icon, iconId);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static void AddSaveInfo(FillResponse.Builder responseBuilder, FieldCollection fields)
|
||||
{
|
||||
var requiredIds = fields.GetRequiredSaveFields();
|
||||
if(fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var saveBuilder = new SaveInfo.Builder(fields.SaveType, requiredIds);
|
||||
var optionalIds = fields.GetOptionalSaveIds();
|
||||
if(optionalIds.Length > 0)
|
||||
{
|
||||
saveBuilder.SetOptionalIds(optionalIds);
|
||||
}
|
||||
responseBuilder.SetSaveInfo(saveBuilder.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
113
src/Android/Autofill/AutofillService.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Widget;
|
||||
using Bit.App;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
||||
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
||||
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
||||
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
||||
public class AutofillService : global::Android.Service.Autofill.AutofillService
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private ILockService _lockService;
|
||||
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(structure);
|
||||
parser.Parse();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(parser.Uri) || parser.Uri == "androidapp://com.x8bit.bitwarden" ||
|
||||
parser.Uri == "androidapp://android" || !parser.FieldCollection.Fillable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_lockService == null)
|
||||
{
|
||||
_lockService = Resolver.Resolve<ILockService>();
|
||||
}
|
||||
|
||||
List<FilledItem> items = null;
|
||||
var locked = (await _lockService.GetLockTypeAsync(false)) != LockType.None;
|
||||
if(!locked)
|
||||
{
|
||||
if(_cipherService == null)
|
||||
{
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
}
|
||||
|
||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
||||
}
|
||||
|
||||
// build response
|
||||
var response = AutofillHelpers.BuildFillResponse(this, parser, items, locked);
|
||||
callback.OnSuccess(response);
|
||||
}
|
||||
|
||||
public override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(structure);
|
||||
parser.Parse();
|
||||
|
||||
var savedItem = parser.FieldCollection.GetSavedItem();
|
||||
if(savedItem == null)
|
||||
{
|
||||
Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
|
||||
return;
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.ClearTop);
|
||||
intent.PutExtra("autofillFramework", true);
|
||||
intent.PutExtra("autofillFrameworkSave", true);
|
||||
intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
|
||||
switch(savedItem.Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
intent.PutExtra("autofillFrameworkName", parser.Uri
|
||||
.Replace(Constants.AndroidAppProtocol, string.Empty)
|
||||
.Replace("https://", string.Empty)
|
||||
.Replace("http://", string.Empty));
|
||||
intent.PutExtra("autofillFrameworkUri", parser.Uri);
|
||||
intent.PutExtra("autofillFrameworkUsername", savedItem.Login.Username);
|
||||
intent.PutExtra("autofillFrameworkPassword", savedItem.Login.Password);
|
||||
break;
|
||||
case CipherType.Card:
|
||||
intent.PutExtra("autofillFrameworkCardName", savedItem.Card.Name);
|
||||
intent.PutExtra("autofillFrameworkCardNumber", savedItem.Card.Number);
|
||||
intent.PutExtra("autofillFrameworkCardExpMonth", savedItem.Card.ExpMonth);
|
||||
intent.PutExtra("autofillFrameworkCardExpYear", savedItem.Card.ExpYear);
|
||||
intent.PutExtra("autofillFrameworkCardCode", savedItem.Card.Code);
|
||||
break;
|
||||
default:
|
||||
Toast.MakeText(this, "Unable to save this type of form.", ToastLength.Short).Show();
|
||||
return;
|
||||
}
|
||||
StartActivity(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
199
src/Android/Autofill/Field.cs
Normal file
@@ -0,0 +1,199 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Views;
|
||||
using Android.Views.Autofill;
|
||||
using static Android.App.Assist.AssistStructure;
|
||||
using Android.Text;
|
||||
using static Android.Views.ViewStructure;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public class Field
|
||||
{
|
||||
private List<string> _hints;
|
||||
|
||||
public Field(ViewNode node)
|
||||
{
|
||||
Id = node.Id;
|
||||
TrackingId = $"{node.Id}_{node.GetHashCode()}";
|
||||
IdEntry = node.IdEntry;
|
||||
AutofillId = node.AutofillId;
|
||||
AutofillType = node.AutofillType;
|
||||
InputType = node.InputType;
|
||||
Focused = node.IsFocused;
|
||||
Selected = node.IsSelected;
|
||||
Clickable = node.IsClickable;
|
||||
Visible = node.Visibility == ViewStates.Visible;
|
||||
Hints = FilterForSupportedHints(node.GetAutofillHints());
|
||||
Hint = node.Hint;
|
||||
AutofillOptions = node.GetAutofillOptions()?.ToList();
|
||||
HtmlInfo = node.HtmlInfo;
|
||||
Node = node;
|
||||
|
||||
if(node.AutofillValue != null)
|
||||
{
|
||||
if(node.AutofillValue.IsList)
|
||||
{
|
||||
var autofillOptions = node.GetAutofillOptions();
|
||||
if(autofillOptions != null && autofillOptions.Length > 0)
|
||||
{
|
||||
ListValue = node.AutofillValue.ListValue;
|
||||
TextValue = autofillOptions[node.AutofillValue.ListValue];
|
||||
}
|
||||
}
|
||||
else if(node.AutofillValue.IsDate)
|
||||
{
|
||||
DateValue = node.AutofillValue.DateValue;
|
||||
}
|
||||
else if(node.AutofillValue.IsText)
|
||||
{
|
||||
TextValue = node.AutofillValue.TextValue;
|
||||
}
|
||||
else if(node.AutofillValue.IsToggle)
|
||||
{
|
||||
ToggleValue = node.AutofillValue.ToggleValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SaveDataType SaveType { get; set; } = SaveDataType.Generic;
|
||||
public List<string> Hints
|
||||
{
|
||||
get => _hints;
|
||||
set
|
||||
{
|
||||
_hints = value;
|
||||
UpdateSaveTypeFromHints();
|
||||
}
|
||||
}
|
||||
public string Hint { get; set; }
|
||||
public int Id { get; private set; }
|
||||
public string TrackingId { get; private set; }
|
||||
public string IdEntry { get; set; }
|
||||
public AutofillId AutofillId { get; private set; }
|
||||
public AutofillType AutofillType { get; private set; }
|
||||
public InputTypes InputType { get; private set; }
|
||||
public bool Focused { get; private set; }
|
||||
public bool Selected { get; private set; }
|
||||
public bool Clickable { get; private set; }
|
||||
public bool Visible { get; private set; }
|
||||
public List<string> AutofillOptions { get; set; }
|
||||
public string TextValue { get; set; }
|
||||
public long? DateValue { get; set; }
|
||||
public int? ListValue { get; set; }
|
||||
public bool? ToggleValue { get; set; }
|
||||
public HtmlInfo HtmlInfo { get; private set; }
|
||||
public ViewNode Node { get; private set; }
|
||||
|
||||
private void UpdateSaveTypeFromHints()
|
||||
{
|
||||
SaveType = SaveDataType.Generic;
|
||||
if(_hints == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var hint in _hints)
|
||||
{
|
||||
switch(hint)
|
||||
{
|
||||
case View.AutofillHintCreditCardExpirationDate:
|
||||
case View.AutofillHintCreditCardExpirationDay:
|
||||
case View.AutofillHintCreditCardExpirationMonth:
|
||||
case View.AutofillHintCreditCardExpirationYear:
|
||||
case View.AutofillHintCreditCardNumber:
|
||||
case View.AutofillHintCreditCardSecurityCode:
|
||||
SaveType |= SaveDataType.CreditCard;
|
||||
break;
|
||||
case View.AutofillHintEmailAddress:
|
||||
SaveType |= SaveDataType.EmailAddress;
|
||||
break;
|
||||
case View.AutofillHintPhone:
|
||||
case View.AutofillHintName:
|
||||
SaveType |= SaveDataType.Generic;
|
||||
break;
|
||||
case View.AutofillHintPassword:
|
||||
SaveType |= SaveDataType.Password;
|
||||
SaveType &= ~SaveDataType.EmailAddress;
|
||||
SaveType &= ~SaveDataType.Username;
|
||||
break;
|
||||
case View.AutofillHintPostalAddress:
|
||||
case View.AutofillHintPostalCode:
|
||||
SaveType |= SaveDataType.Address;
|
||||
break;
|
||||
case View.AutofillHintUsername:
|
||||
SaveType |= SaveDataType.Username;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ValueIsNull()
|
||||
{
|
||||
return TextValue == null && DateValue == null && ToggleValue == null;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if(this == obj)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(obj == null || GetType() != obj.GetType())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var field = obj as Field;
|
||||
if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ToggleValue != null ? ToggleValue.Equals(field.ToggleValue) : field.ToggleValue == null;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var result = TextValue != null ? TextValue.GetHashCode() : 0;
|
||||
result = 31 * result + (DateValue != null ? DateValue.GetHashCode() : 0);
|
||||
result = 31 * result + (ToggleValue != null ? ToggleValue.GetHashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<string> FilterForSupportedHints(string[] hints)
|
||||
{
|
||||
return hints?.Where(h => IsValidHint(h)).ToList() ?? new List<string>();
|
||||
}
|
||||
|
||||
private static bool IsValidHint(string hint)
|
||||
{
|
||||
switch(hint)
|
||||
{
|
||||
case View.AutofillHintCreditCardExpirationDate:
|
||||
case View.AutofillHintCreditCardExpirationDay:
|
||||
case View.AutofillHintCreditCardExpirationMonth:
|
||||
case View.AutofillHintCreditCardExpirationYear:
|
||||
case View.AutofillHintCreditCardNumber:
|
||||
case View.AutofillHintCreditCardSecurityCode:
|
||||
case View.AutofillHintEmailAddress:
|
||||
case View.AutofillHintPhone:
|
||||
case View.AutofillHintName:
|
||||
case View.AutofillHintPassword:
|
||||
case View.AutofillHintPostalAddress:
|
||||
case View.AutofillHintPostalCode:
|
||||
case View.AutofillHintUsername:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
341
src/Android/Autofill/FieldCollection.cs
Normal file
@@ -0,0 +1,341 @@
|
||||
using System.Collections.Generic;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Views.Autofill;
|
||||
using System.Linq;
|
||||
using Android.Text;
|
||||
using Android.Views;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public class FieldCollection
|
||||
{
|
||||
private List<Field> _passwordFields = null;
|
||||
private List<Field> _usernameFields = null;
|
||||
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
||||
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
||||
|
||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
||||
public SaveDataType SaveType
|
||||
{
|
||||
get
|
||||
{
|
||||
if(FillableForLogin)
|
||||
{
|
||||
return SaveDataType.Password;
|
||||
}
|
||||
else if(FillableForCard)
|
||||
{
|
||||
return SaveDataType.CreditCard;
|
||||
}
|
||||
|
||||
return SaveDataType.Generic;
|
||||
}
|
||||
}
|
||||
public HashSet<string> Hints { get; private set; } = new HashSet<string>();
|
||||
public HashSet<string> FocusedHints { get; private set; } = new HashSet<string>();
|
||||
public HashSet<string> FieldTrackingIds { get; private set; } = new HashSet<string>();
|
||||
public List<Field> Fields { get; private set; } = new List<Field>();
|
||||
public IDictionary<string, List<Field>> HintToFieldsMap { get; private set; } =
|
||||
new Dictionary<string, List<Field>>();
|
||||
public List<AutofillId> IgnoreAutofillIds { get; private set; } = new List<AutofillId>();
|
||||
|
||||
public List<Field> PasswordFields
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_passwordFields != null)
|
||||
{
|
||||
return _passwordFields;
|
||||
}
|
||||
|
||||
if(Hints.Any())
|
||||
{
|
||||
_passwordFields = new List<Field>();
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||
{
|
||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||
if(!_passwordFields.Any())
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return _passwordFields;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Field> UsernameFields
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_usernameFields != null)
|
||||
{
|
||||
return _usernameFields;
|
||||
}
|
||||
|
||||
_usernameFields = new List<Field>();
|
||||
if(Hints.Any())
|
||||
{
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
|
||||
{
|
||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintEmailAddress]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
|
||||
{
|
||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach(var passwordField in PasswordFields)
|
||||
{
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||
.LastOrDefault();
|
||||
if(usernameField != null)
|
||||
{
|
||||
_usernameFields.Add(usernameField);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _usernameFields;
|
||||
}
|
||||
}
|
||||
|
||||
public bool FillableForLogin => FocusedHintsContain(
|
||||
new string[] { View.AutofillHintUsername, View.AutofillHintEmailAddress, View.AutofillHintPassword }) ||
|
||||
UsernameFields.Any(f => f.Focused) || PasswordFields.Any(f => f.Focused);
|
||||
public bool FillableForCard => FocusedHintsContain(
|
||||
new string[] { View.AutofillHintCreditCardNumber, View.AutofillHintCreditCardExpirationMonth,
|
||||
View.AutofillHintCreditCardExpirationYear, View.AutofillHintCreditCardSecurityCode});
|
||||
public bool FillableForIdentity => FocusedHintsContain(
|
||||
new string[] { View.AutofillHintName, View.AutofillHintPhone, View.AutofillHintPostalAddress,
|
||||
View.AutofillHintPostalCode });
|
||||
|
||||
public bool Fillable => FillableForLogin || FillableForCard || FillableForIdentity;
|
||||
|
||||
public void Add(Field field)
|
||||
{
|
||||
if(field == null || FieldTrackingIds.Contains(field.TrackingId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_passwordFields = _usernameFields = null;
|
||||
|
||||
FieldTrackingIds.Add(field.TrackingId);
|
||||
Fields.Add(field);
|
||||
AutofillIds.Add(field.AutofillId);
|
||||
|
||||
if(field.Hints != null)
|
||||
{
|
||||
foreach(var hint in field.Hints)
|
||||
{
|
||||
Hints.Add(hint);
|
||||
if(field.Focused)
|
||||
{
|
||||
FocusedHints.Add(hint);
|
||||
}
|
||||
|
||||
if(!HintToFieldsMap.ContainsKey(hint))
|
||||
{
|
||||
HintToFieldsMap.Add(hint, new List<Field>());
|
||||
}
|
||||
|
||||
HintToFieldsMap[hint].Add(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public SavedItem GetSavedItem()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
{
|
||||
var passwordField = PasswordFields.FirstOrDefault(f => !string.IsNullOrWhiteSpace(f.TextValue));
|
||||
if(passwordField == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var savedItem = new SavedItem
|
||||
{
|
||||
Type = App.Enums.CipherType.Login,
|
||||
Login = new SavedItem.LoginItem
|
||||
{
|
||||
Password = GetFieldValue(passwordField)
|
||||
}
|
||||
};
|
||||
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
||||
savedItem.Login.Username = GetFieldValue(usernameField);
|
||||
|
||||
return savedItem;
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard)
|
||||
{
|
||||
var savedItem = new SavedItem
|
||||
{
|
||||
Type = App.Enums.CipherType.Card,
|
||||
Card = new SavedItem.CardItem
|
||||
{
|
||||
Number = GetFieldValue(View.AutofillHintCreditCardNumber),
|
||||
Name = GetFieldValue(View.AutofillHintName),
|
||||
ExpMonth = GetFieldValue(View.AutofillHintCreditCardExpirationMonth, true),
|
||||
ExpYear = GetFieldValue(View.AutofillHintCreditCardExpirationYear),
|
||||
Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode)
|
||||
}
|
||||
};
|
||||
|
||||
return savedItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public AutofillId[] GetOptionalSaveIds()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
{
|
||||
return UsernameFields.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard)
|
||||
{
|
||||
var fieldList = new List<Field>();
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]);
|
||||
}
|
||||
if(HintToFieldsMap.ContainsKey(View.AutofillHintName))
|
||||
{
|
||||
fieldList.AddRange(HintToFieldsMap[View.AutofillHintName]);
|
||||
}
|
||||
return fieldList.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
|
||||
return new AutofillId[0];
|
||||
}
|
||||
|
||||
public AutofillId[] GetRequiredSaveFields()
|
||||
{
|
||||
if(SaveType == SaveDataType.Password)
|
||||
{
|
||||
return PasswordFields.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
else if(SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
|
||||
{
|
||||
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
|
||||
return new AutofillId[0];
|
||||
}
|
||||
|
||||
private bool FocusedHintsContain(IEnumerable<string> hints)
|
||||
{
|
||||
return hints.Any(h => FocusedHints.Contains(h));
|
||||
}
|
||||
|
||||
private string GetFieldValue(string hint, bool monthValue = false)
|
||||
{
|
||||
if(HintToFieldsMap.ContainsKey(hint))
|
||||
{
|
||||
foreach(var field in HintToFieldsMap[hint])
|
||||
{
|
||||
var val = GetFieldValue(field, monthValue);
|
||||
if(!string.IsNullOrWhiteSpace(val))
|
||||
{
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string GetFieldValue(Field field, bool monthValue = false)
|
||||
{
|
||||
if(field == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(field.TextValue))
|
||||
{
|
||||
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
||||
{
|
||||
if(field.AutofillOptions.Count == 13)
|
||||
{
|
||||
return field.ListValue.ToString();
|
||||
}
|
||||
else if(field.AutofillOptions.Count == 12)
|
||||
{
|
||||
return (field.ListValue + 1).ToString();
|
||||
}
|
||||
}
|
||||
return field.TextValue;
|
||||
}
|
||||
else if(field.DateValue.HasValue)
|
||||
{
|
||||
return field.DateValue.Value.ToString();
|
||||
}
|
||||
else if(field.ToggleValue.HasValue)
|
||||
{
|
||||
return field.ToggleValue.Value.ToString();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool FieldIsPassword(Field f)
|
||||
{
|
||||
var inputTypePassword = f.InputType.HasFlag(InputTypes.TextVariationPassword) ||
|
||||
f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) ||
|
||||
f.InputType.HasFlag(InputTypes.TextVariationWebPassword);
|
||||
|
||||
if(!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
|
||||
(f.HtmlInfo.Attributes?.Any() ?? false))
|
||||
{
|
||||
foreach(var a in f.HtmlInfo.Attributes)
|
||||
{
|
||||
var key = a.First as Java.Lang.String;
|
||||
var val = a.Second as Java.Lang.String;
|
||||
if(key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
|
||||
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms);
|
||||
}
|
||||
|
||||
private bool FieldHasPasswordTerms(Field f)
|
||||
{
|
||||
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
|
||||
}
|
||||
|
||||
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lowerValue = value.ToLowerInvariant();
|
||||
return terms.Any(t => lowerValue.Contains(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
273
src/Android/Autofill/FilledItem.cs
Normal file
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Views.Autofill;
|
||||
using System.Linq;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
using Android.Views;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public class FilledItem
|
||||
{
|
||||
private Lazy<string> _password;
|
||||
private Lazy<string> _cardName;
|
||||
private string _cardNumber;
|
||||
private Lazy<string> _cardExpMonth;
|
||||
private Lazy<string> _cardExpYear;
|
||||
private Lazy<string> _cardCode;
|
||||
private Lazy<string> _idPhone;
|
||||
private Lazy<string> _idEmail;
|
||||
private Lazy<string> _idUsername;
|
||||
private Lazy<string> _idAddress;
|
||||
private Lazy<string> _idPostalCode;
|
||||
|
||||
public FilledItem(Cipher cipher)
|
||||
{
|
||||
Name = cipher.Name?.Decrypt(cipher.OrganizationId) ?? "--";
|
||||
Type = cipher.Type;
|
||||
|
||||
switch(Type)
|
||||
{
|
||||
case CipherType.Login:
|
||||
Subtitle = cipher.Login.Username?.Decrypt(cipher.OrganizationId) ?? string.Empty;
|
||||
Icon = Resource.Drawable.login;
|
||||
_password = new Lazy<string>(() => cipher.Login.Password?.Decrypt(cipher.OrganizationId));
|
||||
break;
|
||||
case CipherType.Card:
|
||||
Subtitle = cipher.Card.Brand?.Decrypt(cipher.OrganizationId);
|
||||
_cardNumber = cipher.Card.Number?.Decrypt(cipher.OrganizationId);
|
||||
if(!string.IsNullOrWhiteSpace(_cardNumber) && _cardNumber.Length >= 4)
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(_cardNumber))
|
||||
{
|
||||
Subtitle += ", ";
|
||||
}
|
||||
Subtitle += ("*" + _cardNumber.Substring(_cardNumber.Length - 4));
|
||||
}
|
||||
Icon = Resource.Drawable.card;
|
||||
_cardName = new Lazy<string>(() => cipher.Card.CardholderName?.Decrypt(cipher.OrganizationId));
|
||||
_cardCode = new Lazy<string>(() => cipher.Card.Code?.Decrypt(cipher.OrganizationId));
|
||||
_cardExpMonth = new Lazy<string>(() => cipher.Card.ExpMonth?.Decrypt(cipher.OrganizationId));
|
||||
_cardExpYear = new Lazy<string>(() => cipher.Card.ExpYear?.Decrypt(cipher.OrganizationId));
|
||||
break;
|
||||
case CipherType.Identity:
|
||||
var firstName = cipher.Identity?.FirstName?.Decrypt(cipher.OrganizationId) ?? " ";
|
||||
var lastName = cipher.Identity?.LastName?.Decrypt(cipher.OrganizationId) ?? " ";
|
||||
Subtitle = " ";
|
||||
if(!string.IsNullOrWhiteSpace(firstName))
|
||||
{
|
||||
Subtitle = firstName;
|
||||
}
|
||||
if(!string.IsNullOrWhiteSpace(lastName))
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
Subtitle += " ";
|
||||
}
|
||||
Subtitle += lastName;
|
||||
}
|
||||
Icon = Resource.Drawable.id;
|
||||
_idPhone = new Lazy<string>(() => cipher.Identity.Phone?.Decrypt(cipher.OrganizationId));
|
||||
_idEmail = new Lazy<string>(() => cipher.Identity.Email?.Decrypt(cipher.OrganizationId));
|
||||
_idUsername = new Lazy<string>(() => cipher.Identity.Username?.Decrypt(cipher.OrganizationId));
|
||||
_idAddress = new Lazy<string>(() =>
|
||||
{
|
||||
var address = cipher.Identity.Address1?.Decrypt(cipher.OrganizationId);
|
||||
|
||||
var address2 = cipher.Identity.Address2?.Decrypt(cipher.OrganizationId);
|
||||
if(!string.IsNullOrWhiteSpace(address2))
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
address += ", ";
|
||||
}
|
||||
|
||||
address += address2;
|
||||
}
|
||||
|
||||
var address3 = cipher.Identity.Address3?.Decrypt(cipher.OrganizationId);
|
||||
if(!string.IsNullOrWhiteSpace(address3))
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(address))
|
||||
{
|
||||
address += ", ";
|
||||
}
|
||||
|
||||
address += address3;
|
||||
}
|
||||
|
||||
return address;
|
||||
});
|
||||
_idPostalCode = new Lazy<string>(() => cipher.Identity.PostalCode?.Decrypt(cipher.OrganizationId));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public string Name { get; set; }
|
||||
public string Subtitle { get; set; } = string.Empty;
|
||||
public int Icon { get; set; } = Resource.Drawable.login;
|
||||
public CipherType Type { get; set; }
|
||||
|
||||
public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
|
||||
{
|
||||
if(!fieldCollection?.Fields.Any() ?? true)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var setValues = false;
|
||||
if(Type == CipherType.Login)
|
||||
{
|
||||
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password.Value))
|
||||
{
|
||||
foreach(var f in fieldCollection.PasswordFields)
|
||||
{
|
||||
var val = ApplyValue(f, _password.Value);
|
||||
if(val != null)
|
||||
{
|
||||
setValues = true;
|
||||
datasetBuilder.SetValue(f.AutofillId, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
foreach(var f in fieldCollection.UsernameFields)
|
||||
{
|
||||
var val = ApplyValue(f, Subtitle);
|
||||
if(val != null)
|
||||
{
|
||||
setValues = true;
|
||||
datasetBuilder.SetValue(f.AutofillId, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if(Type == CipherType.Card)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardNumber,
|
||||
new Lazy<string>(() => _cardNumber)))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardSecurityCode, _cardCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationYear, _cardExpYear))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, _cardName))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
}
|
||||
else if(Type == CipherType.Identity)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPhone, _idPhone))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintEmailAddress, _idEmail))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintUsername, _idUsername))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalAddress, _idAddress))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalCode, _idPostalCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, new Lazy<string>(() => Subtitle)))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
}
|
||||
|
||||
return setValues;
|
||||
}
|
||||
|
||||
private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection,
|
||||
string hint, Lazy<string> value, bool monthValue = false)
|
||||
{
|
||||
bool setValues = false;
|
||||
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value.Value))
|
||||
{
|
||||
foreach(var f in fieldCollection.HintToFieldsMap[hint])
|
||||
{
|
||||
var val = ApplyValue(f, value.Value, monthValue);
|
||||
if(val != null)
|
||||
{
|
||||
setValues = true;
|
||||
builder.SetValue(f.AutofillId, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
return setValues;
|
||||
}
|
||||
|
||||
private static AutofillValue ApplyValue(Field field, string value, bool monthValue = false)
|
||||
{
|
||||
switch(field.AutofillType)
|
||||
{
|
||||
case AutofillType.Date:
|
||||
if(long.TryParse(value, out long dateValue))
|
||||
{
|
||||
return AutofillValue.ForDate(dateValue);
|
||||
}
|
||||
break;
|
||||
case AutofillType.List:
|
||||
if(field.AutofillOptions != null)
|
||||
{
|
||||
if(monthValue && int.TryParse(value, out int monthIndex))
|
||||
{
|
||||
if(field.AutofillOptions.Count == 13)
|
||||
{
|
||||
return AutofillValue.ForList(monthIndex);
|
||||
}
|
||||
else if(field.AutofillOptions.Count >= monthIndex)
|
||||
{
|
||||
return AutofillValue.ForList(monthIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < field.AutofillOptions.Count; i++)
|
||||
{
|
||||
if(field.AutofillOptions[i].Equals(value))
|
||||
{
|
||||
return AutofillValue.ForList(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case AutofillType.Text:
|
||||
return AutofillValue.ForText(value);
|
||||
case AutofillType.Toggle:
|
||||
if(bool.TryParse(value, out bool toggleValue))
|
||||
{
|
||||
return AutofillValue.ForToggle(toggleValue);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
135
src/Android/Autofill/Parser.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using static Android.App.Assist.AssistStructure;
|
||||
using Android.App.Assist;
|
||||
using Bit.App;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public class Parser
|
||||
{
|
||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
||||
{
|
||||
"org.mozilla.focus","org.mozilla.firefox","org.mozilla.firefox_beta","com.microsoft.emmx",
|
||||
"com.android.chrome","com.chrome.beta","com.android.browser","com.brave.browser","com.opera.browser",
|
||||
"com.opera.browser.beta","com.opera.mini.native","com.chrome.dev","com.chrome.canary",
|
||||
"com.google.android.apps.chrome","com.google.android.apps.chrome_dev","com.yandex.browser",
|
||||
"com.sec.android.app.sbrowser","com.sec.android.app.sbrowser.beta","org.codeaurora.swe.browser",
|
||||
"com.amazon.cloud9","org.mozilla.klar","com.duckduckgo.mobile.android","mark.via.gp","org.bromite.bromite"
|
||||
};
|
||||
|
||||
public static HashSet<string> ExcludedPackageIds = new HashSet<string>
|
||||
{
|
||||
"android"
|
||||
};
|
||||
|
||||
private readonly AssistStructure _structure;
|
||||
private string _uri;
|
||||
private string _packageName;
|
||||
private string _webDomain;
|
||||
|
||||
public Parser(AssistStructure structure)
|
||||
{
|
||||
_structure = structure;
|
||||
}
|
||||
|
||||
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
|
||||
public string Uri
|
||||
{
|
||||
get
|
||||
{
|
||||
if(!string.IsNullOrWhiteSpace(_uri))
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(WebDomain) && string.IsNullOrWhiteSpace(PackageName))
|
||||
{
|
||||
_uri = null;
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(WebDomain))
|
||||
{
|
||||
_uri = string.Concat("http://", WebDomain);
|
||||
}
|
||||
else
|
||||
{
|
||||
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
||||
}
|
||||
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
public string PackageName
|
||||
{
|
||||
get => _packageName;
|
||||
set
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_packageName = _uri = null;
|
||||
}
|
||||
|
||||
_packageName = value;
|
||||
}
|
||||
}
|
||||
public string WebDomain
|
||||
{
|
||||
get => _webDomain;
|
||||
set
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
_webDomain = _uri = null;
|
||||
}
|
||||
|
||||
_webDomain = value;
|
||||
}
|
||||
}
|
||||
|
||||
public void Parse()
|
||||
{
|
||||
for(var i = 0; i < _structure.WindowNodeCount; i++)
|
||||
{
|
||||
var node = _structure.GetWindowNodeAt(i);
|
||||
ParseNode(node.RootViewNode);
|
||||
}
|
||||
|
||||
if(!TrustedBrowsers.Contains(PackageName))
|
||||
{
|
||||
WebDomain = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseNode(ViewNode node)
|
||||
{
|
||||
SetPackageAndDomain(node);
|
||||
var hints = node.GetAutofillHints();
|
||||
var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input";
|
||||
if(isEditText || (hints?.Length ?? 0) > 0)
|
||||
{
|
||||
FieldCollection.Add(new Field(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
FieldCollection.IgnoreAutofillIds.Add(node.AutofillId);
|
||||
}
|
||||
|
||||
for(var i = 0; i < node.ChildCount; i++)
|
||||
{
|
||||
ParseNode(node.GetChildAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPackageAndDomain(ViewNode node)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
|
||||
!ExcludedPackageIds.Contains(node.IdPackage))
|
||||
{
|
||||
PackageName = node.IdPackage;
|
||||
}
|
||||
if(string.IsNullOrWhiteSpace(WebDomain) && !string.IsNullOrWhiteSpace(node.WebDomain))
|
||||
{
|
||||
WebDomain = node.WebDomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Android/Autofill/SavedItem.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
{
|
||||
public class SavedItem
|
||||
{
|
||||
public CipherType Type { get; set; }
|
||||
public LoginItem Login { get; set; }
|
||||
public CardItem Card { get; set; }
|
||||
|
||||
public class LoginItem
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
|
||||
public class CardItem
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Number { get; set; }
|
||||
public string ExpMonth { get; set; }
|
||||
public string ExpYear { get; set; }
|
||||
public string Code { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
101
src/Android/AutofillActivity.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Activity(Theme = "@style/BitwardenTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
public class AutofillActivity : Activity
|
||||
{
|
||||
private string _lastQueriedUri;
|
||||
|
||||
public static AutofillCredentials LastCredentials { get; set; }
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
LaunchMainActivity(Intent, 932473);
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
LaunchMainActivity(intent, 489729);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
base.OnResume();
|
||||
if(!Intent.HasExtra("uri"))
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent.RemoveExtra("uri");
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
|
||||
{
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
if(data == null)
|
||||
{
|
||||
LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if(data.GetStringExtra("canceled") != null)
|
||||
{
|
||||
LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var uri = data.GetStringExtra("uri");
|
||||
var username = data.GetStringExtra("username");
|
||||
var password = data.GetStringExtra("password");
|
||||
|
||||
LastCredentials = new AutofillCredentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password,
|
||||
Uri = uri,
|
||||
LastUri = _lastQueriedUri
|
||||
};
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
LastCredentials = null;
|
||||
}
|
||||
}
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
private void LaunchMainActivity(Intent callingIntent, int requestCode)
|
||||
{
|
||||
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
|
||||
if(_lastQueriedUri == null)
|
||||
{
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
{
|
||||
intent.PutExtra("uri", _lastQueriedUri);
|
||||
}
|
||||
StartActivityForResult(intent, requestCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
namespace Bit.App.Models.Api
|
||||
namespace Bit.Android
|
||||
{
|
||||
public class SiteDataModel
|
||||
public class AutofillCredentials
|
||||
{
|
||||
public string Name { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
public string Notes { get; set; }
|
||||
public string Uri { get; set; }
|
||||
public string LastUri { get; set; }
|
||||
}
|
||||
}
|
||||
541
src/Android/AutofillService.cs
Normal file
@@ -0,0 +1,541 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Android.AccessibilityServices;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Views.Accessibility;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Bit.App.Resources;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Service(Permission = global::Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||
public class AutofillService : AccessibilityService
|
||||
{
|
||||
private NotificationChannel _notificationChannel;
|
||||
|
||||
private const string BitwardenTag = "bw_access";
|
||||
private const int AutoFillNotificationId = 34573;
|
||||
private const string SystemUiPackage = "com.android.systemui";
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "vault.bitwarden.com";
|
||||
|
||||
private static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||
{
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("com.android.browser", "url"),
|
||||
new Browser("com.brave.browser", "url_bar"),
|
||||
new Browser("com.opera.browser", "url_field"),
|
||||
new Browser("com.opera.browser.beta", "url_field"),
|
||||
new Browser("com.opera.mini.native", "url_field"),
|
||||
new Browser("com.chrome.dev", "url_bar"),
|
||||
new Browser("com.chrome.canary", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||
new Browser("org.iron.srware", "url_bar"),
|
||||
new Browser("com.sec.android.app.sbrowser", "location_bar_edit_text"),
|
||||
new Browser("com.sec.android.app.sbrowser.beta", "location_bar_edit_text"),
|
||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text",
|
||||
(s) => s.Split(new char[]{' ', '<27>'}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
||||
new Browser("org.mozilla.firefox", "url_bar_title"),
|
||||
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
|
||||
new Browser("org.mozilla.focus", "display_url"),
|
||||
new Browser("org.mozilla.klar", "display_url"),
|
||||
new Browser("com.ghostery.android.ghostery", "search_field"),
|
||||
new Browser("org.adblockplus.browser", "url_bar_title"),
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("mobi.mgeek.TunnyBrowser", "title"),
|
||||
new Browser("com.nubelacorp.javelin", "enterUrl"),
|
||||
new Browser("com.jerky.browser2", "enterUrl"),
|
||||
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||
new Browser("com.linkbubble.playstore", "url_text"),
|
||||
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||
new Browser("acr.browser.lightning", "search"),
|
||||
new Browser("acr.browser.barebones", "search"),
|
||||
new Browser("com.microsoft.emmx", "url_bar"),
|
||||
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
|
||||
new Browser("mark.via.gp", "aw"),
|
||||
new Browser("org.bromite.bromite", "url_bar"),
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
// Known packages to skip
|
||||
private static HashSet<string> FilteredPackageNames => new HashSet<string>
|
||||
{
|
||||
SystemUiPackage,
|
||||
"com.google.android.googlequicksearchbox",
|
||||
"com.google.android.apps.nexuslauncher",
|
||||
"com.google.android.launcher",
|
||||
"com.computer.desktop.ui.launcher",
|
||||
"com.launcher.notelauncher",
|
||||
"com.anddoes.launcher",
|
||||
"com.actionlauncher.playstore",
|
||||
"ch.deletescape.lawnchair.plah",
|
||||
"com.microsoft.launcher",
|
||||
"com.teslacoilsw.launcher",
|
||||
"com.teslacoilsw.launcher.prime",
|
||||
"is.shortcut",
|
||||
"me.craftsapp.nlauncher",
|
||||
"com.ss.squarehome2"
|
||||
};
|
||||
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
private long _lastNotificationTime = 0;
|
||||
private string _lastNotificationUri = null;
|
||||
private HashSet<string> _launcherPackageNames = null;
|
||||
private DateTime? _lastLauncherSetBuilt = null;
|
||||
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
||||
|
||||
public AutofillService()
|
||||
{
|
||||
_appSettings = Resolver.Resolve<IAppSettingsService>();
|
||||
}
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
var powerManager = (PowerManager)GetSystemService(PowerService);
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if(SkipPackage(e?.PackageName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var root = RootInActiveWindow;
|
||||
if(root == null || root.PackageName != e.PackageName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
|
||||
//var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
|
||||
//testNodes.Dispose();
|
||||
|
||||
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
|
||||
var cancelNotification = true;
|
||||
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.ViewFocused:
|
||||
if(e.Source == null || !e.Source.Password || !_appSettings.AutofillPasswordField)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
|
||||
if(ScanAndAutofill(root, e, notificationManager, cancelNotification))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
break;
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
if(_appSettings.AutofillPasswordField && e.Source.Password)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(_appSettings.AutofillPasswordField && AutofillActivity.LastCredentials == null)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(_lastNotificationUri))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
|
||||
var uri = GetUri(root);
|
||||
if(uri != _lastNotificationUri)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
else if(uri.StartsWith(App.Constants.AndroidAppProtocol))
|
||||
{
|
||||
CancelNotification(notificationManager, 30000);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
|
||||
if(_appSettings.AutofillPersistNotification)
|
||||
{
|
||||
var uri = GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
var needToFill = NeedToAutofill(AutofillActivity.LastCredentials, uri);
|
||||
if(needToFill)
|
||||
{
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||
needToFill = passwordNodes.Any();
|
||||
if(needToFill)
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText.Dispose();
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
}
|
||||
|
||||
if(!needToFill)
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
cancelNotification = ScanAndAutofill(root, e, notificationManager, cancelNotification);
|
||||
}
|
||||
|
||||
if(cancelNotification)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
notificationManager?.Dispose();
|
||||
root.Dispose();
|
||||
e.Dispose();
|
||||
}
|
||||
// Suppress exceptions so that service doesn't crash
|
||||
catch { }
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||
NotificationManager notificationManager, bool cancelNotification)
|
||||
{
|
||||
var passwordNodes = GetWindowNodes(root, e, n => n.Password, false);
|
||||
if(passwordNodes.Count > 0)
|
||||
{
|
||||
var uri = GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
|
||||
AutofillActivity.LastCredentials = null;
|
||||
}
|
||||
else if(AutofillActivity.LastCredentials != null)
|
||||
{
|
||||
System.Threading.Tasks.Task.Run(async () =>
|
||||
{
|
||||
await System.Threading.Tasks.Task.Delay(1000);
|
||||
AutofillActivity.LastCredentials = null;
|
||||
});
|
||||
}
|
||||
|
||||
passwordNodes.Dispose();
|
||||
return cancelNotification;
|
||||
}
|
||||
|
||||
public void CancelNotification(NotificationManager notificationManager, long limit = 250)
|
||||
{
|
||||
if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastNotificationTime < limit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastNotificationUri = null;
|
||||
notificationManager?.Cancel(AutoFillNotificationId);
|
||||
}
|
||||
|
||||
private string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{SupportedBrowsers[root.PackageName].UriViewId}").FirstOrDefault();
|
||||
if(addressNode != null)
|
||||
{
|
||||
uri = ExtractUri(uri, addressNode, SupportedBrowsers[root.PackageName]);
|
||||
addressNode.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
{
|
||||
if(addressNode?.Text != null)
|
||||
{
|
||||
uri = browser.GetUriFunction(addressNode.Text).Trim();
|
||||
if(uri != null && uri.Contains("."))
|
||||
{
|
||||
if(!uri.Contains("://") && !uri.Contains(" "))
|
||||
{
|
||||
uri = string.Concat("http://", uri);
|
||||
}
|
||||
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
|
||||
if(parts.Length > 1)
|
||||
{
|
||||
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
|
||||
if(urlPart != null)
|
||||
{
|
||||
uri = urlPart.Trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uri;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check to make sure it is ok to autofill still on the current screen
|
||||
/// </summary>
|
||||
private bool NeedToAutofill(AutofillCredentials creds, string currentUriString)
|
||||
{
|
||||
if(creds == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Uri lastUri, currentUri;
|
||||
if(Uri.TryCreate(creds.LastUri, UriKind.Absolute, out lastUri) &&
|
||||
Uri.TryCreate(currentUriString, UriKind.Absolute, out currentUri) &&
|
||||
lastUri.Host == currentUri.Host)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool EditText(AccessibilityNodeInfo n)
|
||||
{
|
||||
return n?.ClassName?.Contains("EditText") ?? false;
|
||||
}
|
||||
|
||||
private void NotifyToAutofill(string uri, NotificationManager notificationManager)
|
||||
{
|
||||
if(notificationManager == null || string.IsNullOrWhiteSpace(uri))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var now = Java.Lang.JavaSystem.CurrentTimeMillis();
|
||||
var intent = new Intent(this, typeof(AutofillActivity));
|
||||
intent.PutExtra("uri", uri);
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
|
||||
|
||||
var notificationContent = Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch ?
|
||||
AppResources.BitwardenAutofillServiceNotificationContent :
|
||||
AppResources.BitwardenAutofillServiceNotificationContentOld;
|
||||
|
||||
var builder = new Notification.Builder(this);
|
||||
builder.SetSmallIcon(Resource.Drawable.notification_sm)
|
||||
.SetContentTitle(AppResources.BitwardenAutofillService)
|
||||
.SetContentText(notificationContent)
|
||||
.SetTicker(notificationContent)
|
||||
.SetWhen(now)
|
||||
.SetContentIntent(pendingIntent);
|
||||
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
builder.SetVisibility(NotificationVisibility.Secret)
|
||||
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
|
||||
Resource.Color.primary));
|
||||
}
|
||||
|
||||
if(Build.VERSION.SdkInt >= BuildVersionCodes.O)
|
||||
{
|
||||
if(_notificationChannel == null)
|
||||
{
|
||||
_notificationChannel = new NotificationChannel("bitwarden_autofill_service",
|
||||
AppResources.AutofillService, NotificationImportance.Low);
|
||||
notificationManager.CreateNotificationChannel(_notificationChannel);
|
||||
}
|
||||
builder.SetChannelId(_notificationChannel.Id);
|
||||
}
|
||||
|
||||
if(/*Build.VERSION.SdkInt <= BuildVersionCodes.N && */_appSettings.AutofillPersistNotification)
|
||||
{
|
||||
builder.SetPriority(-2);
|
||||
}
|
||||
|
||||
_lastNotificationTime = now;
|
||||
_lastNotificationUri = uri;
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
|
||||
builder.Dispose();
|
||||
}
|
||||
|
||||
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
FillEditText(usernameNode, AutofillActivity.LastCredentials?.Username);
|
||||
foreach(var n in passwordNodes)
|
||||
{
|
||||
FillEditText(n, AutofillActivity.LastCredentials?.Password);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||
{
|
||||
if(editTextNode == null || value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var bundle = new Bundle();
|
||||
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
||||
editTextNode.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
|
||||
}
|
||||
|
||||
private NodeList GetWindowNodes(AccessibilityNodeInfo n, AccessibilityEvent e,
|
||||
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
|
||||
int recursionDepth = 0)
|
||||
{
|
||||
if(nodes == null)
|
||||
{
|
||||
nodes = new NodeList();
|
||||
}
|
||||
|
||||
var dispose = disposeIfUnused;
|
||||
if(n != null && recursionDepth < 50)
|
||||
{
|
||||
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && condition(n))
|
||||
{
|
||||
dispose = false;
|
||||
nodes.Add(n);
|
||||
}
|
||||
|
||||
for(var i = 0; i < n.ChildCount; i++)
|
||||
{
|
||||
var childNode = n.GetChild(i);
|
||||
if(i > 100)
|
||||
{
|
||||
global::Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
||||
break;
|
||||
}
|
||||
else if(childNode.GetHashCode() == n.GetHashCode())
|
||||
{
|
||||
global::Android.Util.Log.Info(BitwardenTag,
|
||||
"Child node is the same as parent for some reason.");
|
||||
}
|
||||
else
|
||||
{
|
||||
GetWindowNodes(childNode, e, condition, true, nodes, recursionDepth++);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dispose)
|
||||
{
|
||||
n?.Dispose();
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
private bool SkipPackage(string eventPackageName)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(eventPackageName) || FilteredPackageNames.Contains(eventPackageName)
|
||||
|| eventPackageName.Contains("launcher"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if(_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
|
||||
(DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan)
|
||||
{
|
||||
// refresh launcher list every now and then
|
||||
_lastLauncherSetBuilt = DateTime.Now;
|
||||
var intent = new Intent(Intent.ActionMain);
|
||||
intent.AddCategory(Intent.CategoryHome);
|
||||
var resolveInfo = PackageManager.QueryIntentActivities(intent, 0);
|
||||
_launcherPackageNames = resolveInfo.Select(ri => ri.ActivityInfo.PackageName).ToHashSet();
|
||||
}
|
||||
|
||||
return _launcherPackageNames.Contains(eventPackageName);
|
||||
}
|
||||
|
||||
public class Browser
|
||||
{
|
||||
public Browser(string packageName, string uriViewId)
|
||||
{
|
||||
PackageName = packageName;
|
||||
UriViewId = uriViewId;
|
||||
}
|
||||
|
||||
public Browser(string packageName, string uriViewId, Func<string, string> getUriFunction)
|
||||
: this(packageName, uriViewId)
|
||||
{
|
||||
GetUriFunction = getUriFunction;
|
||||
}
|
||||
|
||||
public string PackageName { get; set; }
|
||||
public string UriViewId { get; set; }
|
||||
public Func<string, string> GetUriFunction { get; set; } = (s) => s;
|
||||
}
|
||||
|
||||
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in this)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
35
src/Android/Controls/CustomButtonRenderer.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Bit.Android.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Button), typeof(CustomButtonRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class CustomButtonRenderer : ButtonRenderer
|
||||
{
|
||||
public CustomButtonRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control.TextSize == (float)Device.GetNamedSize(NamedSize.Default, typeof(Button)))
|
||||
{
|
||||
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Button));
|
||||
}
|
||||
|
||||
// This will prevent all screen overlay apps from being able to interact with buttons.
|
||||
// Ex: apps that change the screen color for "night mode"
|
||||
// Control.FilterTouchesWhenObscured = true;
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
src/Android/Controls/CustomLabelRenderer.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Android.Content;
|
||||
using Bit.Android.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Label), typeof(CustomLabelRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class CustomLabelRenderer : LabelRenderer
|
||||
{
|
||||
public CustomLabelRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
Control.SetMaxLines(int.MaxValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Android/Controls/CustomSearchBarRenderer.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Bit.Android.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||
{
|
||||
public CustomSearchBarRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
Control.SetPadding((int)global::Android.App.Application.Context.ToPixels(-8), 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/Android/Controls/CustomSliderRenderer.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Android.Content;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Support.V4.Content.Res;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Slider), typeof(CustomSliderRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class CustomSliderRenderer : SliderRenderer
|
||||
{
|
||||
public CustomSliderRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null)
|
||||
{
|
||||
var thumb = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
||||
Control.SetThumb(thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
67
src/Android/Controls/ExtendedButtonRenderer.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedButton), typeof(ExtendedButtonRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedButtonRenderer : CustomButtonRenderer
|
||||
{
|
||||
public ExtendedButtonRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Button> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
SetPadding();
|
||||
SetUppercase();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == ExtendedButton.PaddingProperty.PropertyName)
|
||||
{
|
||||
SetPadding();
|
||||
}
|
||||
else if(e.PropertyName == ExtendedButton.UppercaseProperty.PropertyName)
|
||||
{
|
||||
SetUppercase();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPadding()
|
||||
{
|
||||
var element = Element as ExtendedButton;
|
||||
if(element != null)
|
||||
{
|
||||
Control.SetPadding(
|
||||
(int)element.Padding.Left,
|
||||
(int)element.Padding.Top,
|
||||
(int)element.Padding.Right,
|
||||
(int)element.Padding.Bottom);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetUppercase()
|
||||
{
|
||||
var element = Element as ExtendedButton;
|
||||
if(element != null && !string.IsNullOrWhiteSpace(element.Text))
|
||||
{
|
||||
if(element.Uppercase)
|
||||
{
|
||||
element.Text = element.Text.ToUpperInvariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
Control.TransformationMethod = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,19 @@ using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Text.Method;
|
||||
using Android.Views;
|
||||
using Android.Content;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedEditor), typeof(ExtendedEditorRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedEditorRenderer : EditorRenderer
|
||||
{
|
||||
public ExtendedEditorRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
@@ -17,6 +24,7 @@ namespace Bit.Android.Controls
|
||||
var view = (ExtendedEditor)Element;
|
||||
|
||||
SetBorder(view);
|
||||
SetScrollable();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
@@ -44,5 +52,35 @@ namespace Bit.Android.Controls
|
||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetScrollable()
|
||||
{
|
||||
// While scrolling inside Editor stop scrolling parent view.
|
||||
Control.OverScrollMode = OverScrollMode.Always;
|
||||
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
||||
Control.SetOnTouchListener(new EditorTouchListener());
|
||||
|
||||
// For Scrolling in Editor innner area
|
||||
Control.VerticalScrollBarEnabled = true;
|
||||
Control.ScrollBarStyle = ScrollbarStyles.InsideInset;
|
||||
|
||||
// Force scrollbars to be displayed
|
||||
var arr = Control.Context.Theme.ObtainStyledAttributes(new int[0]);
|
||||
InitializeScrollbars(arr);
|
||||
arr.Recycle();
|
||||
}
|
||||
|
||||
public class EditorTouchListener : Java.Lang.Object, IOnTouchListener
|
||||
{
|
||||
public bool OnTouch(global::Android.Views.View v, MotionEvent e)
|
||||
{
|
||||
v.Parent?.RequestDisallowInterceptTouchEvent(true);
|
||||
if((e.Action & MotionEventActions.Up) != 0 && (e.ActionMasked & MotionEventActions.Up) != 0)
|
||||
{
|
||||
v.Parent?.RequestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.Text;
|
||||
using Android.Text.Method;
|
||||
@@ -8,6 +9,7 @@ using Android.Widget;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Bit.App.Enums;
|
||||
using Plugin.CurrentActivity;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
@@ -16,43 +18,107 @@ namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedEntryRenderer : EntryRenderer
|
||||
{
|
||||
public ExtendedEntryRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
private bool _isPassword;
|
||||
private bool _toggledPassword;
|
||||
private bool _isDisposed;
|
||||
private ExtendedEntry _view;
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
var view = (ExtendedEntry)Element;
|
||||
_view = (ExtendedEntry)Element;
|
||||
_isPassword = _view.IsPassword;
|
||||
|
||||
if(Control != null && e.NewElement != null && e.NewElement.IsPassword)
|
||||
if(Control != null)
|
||||
{
|
||||
Control.SetTypeface(Typeface.Default, TypefaceStyle.Normal);
|
||||
Control.TransformationMethod = new PasswordTransformationMethod();
|
||||
Control.SetIncludeFontPadding(false);
|
||||
if(e.NewElement != null && e.NewElement.IsPassword)
|
||||
{
|
||||
Control.SetTypeface(Typeface.Default, TypefaceStyle.Normal);
|
||||
Control.TransformationMethod = new PasswordTransformationMethod();
|
||||
}
|
||||
}
|
||||
|
||||
SetBorder(view);
|
||||
SetMaxLength(view);
|
||||
SetReturnType(view);
|
||||
SetBorder(_view);
|
||||
SetMaxLength(_view);
|
||||
SetReturnType(_view);
|
||||
|
||||
// Editor Action is called when the return button is pressed
|
||||
Control.EditorAction += (object sender, TextView.EditorActionEventArgs args) =>
|
||||
{
|
||||
if(view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
view.Unfocus();
|
||||
}
|
||||
Control.EditorAction += Control_EditorAction;
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
view.InvokeCompleted();
|
||||
};
|
||||
|
||||
if(view.DisableAutocapitalize)
|
||||
if(_view.DisableAutocapitalize)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
||||
}
|
||||
|
||||
if(view.Autocorrect.HasValue)
|
||||
if(_view.Autocorrect.HasValue)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextFlagNoSuggestions);
|
||||
}
|
||||
|
||||
if(_view.IsPassword)
|
||||
{
|
||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||
}
|
||||
|
||||
_view.ToggleIsPassword += ToggleIsPassword;
|
||||
|
||||
if(_view.FontFamily == "monospace")
|
||||
{
|
||||
Control.Typeface = Typeface.Monospace;
|
||||
}
|
||||
|
||||
if(_view.HideCursor)
|
||||
{
|
||||
Control.SetCursorVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleIsPassword(object sender, EventArgs e)
|
||||
{
|
||||
var cursorStart = Control.SelectionStart;
|
||||
var cursorEnd = Control.SelectionEnd;
|
||||
|
||||
Control.TransformationMethod = _isPassword ? null : new PasswordTransformationMethod();
|
||||
Control.SetRawInputType(InputTypes.TextFlagNoSuggestions | InputTypes.TextVariationVisiblePassword);
|
||||
|
||||
// set focus
|
||||
Control.RequestFocus();
|
||||
|
||||
if(_toggledPassword)
|
||||
{
|
||||
// restore cursor position
|
||||
Control.SetSelection(cursorStart, cursorEnd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// set cursor to end
|
||||
Control.SetSelection(Control.Text.Length);
|
||||
}
|
||||
|
||||
// show keyboard
|
||||
var imm = CrossCurrentActivity.Current.Activity.GetSystemService(Context.InputMethodService) as InputMethodManager;
|
||||
imm.ShowSoftInput(Control, ShowFlags.Forced);
|
||||
imm.ToggleSoftInput(ShowFlags.Forced, HideSoftInputFlags.ImplicitOnly);
|
||||
|
||||
_isPassword = _view.IsPasswordFromToggled = !_isPassword;
|
||||
_toggledPassword = true;
|
||||
}
|
||||
|
||||
private void Control_EditorAction(object sender, TextView.EditorActionEventArgs e)
|
||||
{
|
||||
if(_view.ReturnType != ReturnType.Next)
|
||||
{
|
||||
_view.Unfocus();
|
||||
}
|
||||
|
||||
// Call all the methods attached to base_entry event handler Completed
|
||||
_view.InvokeCompleted();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
@@ -73,6 +139,28 @@ namespace Bit.Android.Controls
|
||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
|
||||
if(view.FontFamily == "monospace")
|
||||
{
|
||||
Control.Typeface = Typeface.Monospace;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(_isDisposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
if(disposing && Control != null)
|
||||
{
|
||||
_view.ToggleIsPassword -= ToggleIsPassword;
|
||||
Control.EditorAction -= Control_EditorAction;
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
private void SetReturnType(ExtendedEntry view)
|
||||
|
||||
33
src/Android/Controls/ExtendedListViewRenderer.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedListView), typeof(ExtendedListViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedListViewRenderer : ListViewRenderer
|
||||
{
|
||||
public ExtendedListViewRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(e.NewElement is ExtendedListView listView)
|
||||
{
|
||||
if(listView.BottomPadding > 0)
|
||||
{
|
||||
Control.SetPadding(0, 0, 0, listView.BottomPadding);
|
||||
Control.SetClipToPadding(false);
|
||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Android.App;
|
||||
using Android.Graphics.Drawables;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedNavigationPage), typeof(ExtendedNavigationRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedNavigationRenderer : NavigationRenderer
|
||||
{
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<NavigationPage> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
RemoveAppIconFromActionBar();
|
||||
}
|
||||
|
||||
private void RemoveAppIconFromActionBar()
|
||||
{
|
||||
// ref: http://stackoverflow.com/questions/14606294/remove-icon-logo-from-action-bar-on-android
|
||||
var actionBar = ((Activity)Context).ActionBar;
|
||||
actionBar.SetIcon(new ColorDrawable(Color.Transparent.ToAndroid()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
@@ -10,12 +11,17 @@ namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedPickerRenderer : PickerRenderer
|
||||
{
|
||||
public ExtendedPickerRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
var view = (ExtendedPicker)Element;
|
||||
|
||||
Control.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Picker));
|
||||
SetBorder(view);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,22 +6,52 @@ using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using AView = Android.Views.View;
|
||||
using Android.Widget;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedSwitchCell), typeof(ExtendedSwitchCellRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedSwitchCellRenderer : SwitchCellRenderer
|
||||
{
|
||||
protected AView View { get; private set; }
|
||||
protected BaseCellView View { get; private set; }
|
||||
|
||||
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
||||
{
|
||||
var View = base.GetCellCore(item, convertView, parent, context);
|
||||
var View = base.GetCellCore(item, convertView, parent, context) as SwitchCellView;
|
||||
var extendedCell = (ExtendedSwitchCell)item;
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
if(extendedCell.BackgroundColor != Color.White)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||
}
|
||||
|
||||
if(item.IsEnabled)
|
||||
{
|
||||
View.SetMainTextColor(Color.Black);
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetMainTextColor(Color.FromHex("777777"));
|
||||
}
|
||||
|
||||
if(View.ChildCount > 1)
|
||||
{
|
||||
var layout = View.GetChildAt(1) as LinearLayout;
|
||||
if(layout != null && layout.ChildCount > 0)
|
||||
{
|
||||
var textView = layout.GetChildAt(0) as TextView;
|
||||
if(textView != null)
|
||||
{
|
||||
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View;
|
||||
|
||||
441
src/Android/Controls/ExtendedTabbedPageRenderer.cs
Normal file
@@ -0,0 +1,441 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Com.Ittianyu.Bottomnavigationviewex;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using RelativeLayout = Android.Widget.RelativeLayout;
|
||||
using Platform = Xamarin.Forms.Platform.Android.Platform;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.Support.Design.Internal;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.ComponentModel;
|
||||
using Android.Support.Design.Widget;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedTabbedPage), typeof(ExtendedTabbedPageRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedTabbedPageRenderer : VisualElementRenderer<ExtendedTabbedPage>,
|
||||
BottomNavigationView.IOnNavigationItemSelectedListener
|
||||
{
|
||||
public static bool ShouldUpdateSelectedIcon;
|
||||
public static Action<IMenuItem, FileImageSource, bool> MenuItemIconSetter;
|
||||
public static float? BottomBarHeight = 50;
|
||||
|
||||
private RelativeLayout _rootLayout;
|
||||
private FrameLayout _pageContainer;
|
||||
private BottomNavigationViewEx _bottomNav;
|
||||
private readonly int _barId;
|
||||
|
||||
public static global::Android.Graphics.Color? BackgroundColor;
|
||||
|
||||
public ExtendedTabbedPageRenderer(Context context)
|
||||
: base(context)
|
||||
{
|
||||
AutoPackage = false;
|
||||
_barId = GenerateViewId();
|
||||
}
|
||||
|
||||
IPageController TabbedController => Element as IPageController;
|
||||
public int LastSelectedIndex { get; internal set; }
|
||||
|
||||
public bool OnNavigationItemSelected(IMenuItem item)
|
||||
{
|
||||
this.SwitchPage(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void SetupTabItems()
|
||||
{
|
||||
this.SetupTabItems(_bottomNav);
|
||||
}
|
||||
|
||||
internal void SetupBottomBar()
|
||||
{
|
||||
_bottomNav = this.SetupBottomBar(_rootLayout, _bottomNav, _barId);
|
||||
}
|
||||
|
||||
public static readonly Action<IMenuItem, FileImageSource, bool> DefaultMenuItemIconSetter = (menuItem, icon, selected) =>
|
||||
{
|
||||
var tabIconId = ResourceUtils.IdFromTitle(icon, ResourceManager.DrawableClass);
|
||||
menuItem.SetIcon(tabIconId);
|
||||
};
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<ExtendedTabbedPage> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(e.OldElement != null)
|
||||
{
|
||||
e.OldElement.ChildAdded -= PagesChanged;
|
||||
e.OldElement.ChildRemoved -= PagesChanged;
|
||||
e.OldElement.ChildrenReordered -= PagesChanged;
|
||||
}
|
||||
|
||||
if(e.NewElement == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateIgnoreContainerAreas();
|
||||
|
||||
if(_rootLayout == null)
|
||||
{
|
||||
SetupNativeView();
|
||||
}
|
||||
|
||||
this.HandlePagesChanged();
|
||||
SwitchContent(Element.CurrentPage);
|
||||
|
||||
Element.ChildAdded += PagesChanged;
|
||||
Element.ChildRemoved += PagesChanged;
|
||||
Element.ChildrenReordered += PagesChanged;
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == nameof(TabbedPage.CurrentPage))
|
||||
{
|
||||
SwitchContent(Element.CurrentPage);
|
||||
}
|
||||
}
|
||||
|
||||
void PagesChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.HandlePagesChanged();
|
||||
}
|
||||
|
||||
protected override void OnAttachedToWindow()
|
||||
{
|
||||
base.OnAttachedToWindow();
|
||||
TabbedController?.SendAppearing();
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromWindow()
|
||||
{
|
||||
base.OnDetachedFromWindow();
|
||||
TabbedController?.SendDisappearing();
|
||||
}
|
||||
|
||||
protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
|
||||
{
|
||||
var width = right - left;
|
||||
var height = bottom - top;
|
||||
|
||||
base.OnLayout(changed, left, top, right, bottom);
|
||||
if(width <= 0 || height <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.Layout(width, height);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(disposing)
|
||||
{
|
||||
Element.ChildAdded -= PagesChanged;
|
||||
Element.ChildRemoved -= PagesChanged;
|
||||
Element.ChildrenReordered -= PagesChanged;
|
||||
|
||||
if(_rootLayout != null)
|
||||
{
|
||||
RemoveAllViews();
|
||||
foreach(Page pageToRemove in Element.Children)
|
||||
{
|
||||
var pageRenderer = Platform.GetRenderer(pageToRemove);
|
||||
if(pageRenderer != null)
|
||||
{
|
||||
pageRenderer.View.RemoveFromParent();
|
||||
pageRenderer.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
if(_bottomNav != null)
|
||||
{
|
||||
_bottomNav.SetOnNavigationItemSelectedListener(null);
|
||||
_bottomNav.Dispose();
|
||||
_bottomNav = null;
|
||||
}
|
||||
_rootLayout.Dispose();
|
||||
_rootLayout = null;
|
||||
}
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
internal void SetupNativeView()
|
||||
{
|
||||
_rootLayout = this.CreateRoot(_barId, GenerateViewId(), out _pageContainer);
|
||||
AddView(_rootLayout);
|
||||
}
|
||||
|
||||
void SwitchContent(Page page)
|
||||
{
|
||||
this.ChangePage(_pageContainer, page);
|
||||
}
|
||||
|
||||
void UpdateIgnoreContainerAreas()
|
||||
{
|
||||
foreach(var child in Element.Children)
|
||||
{
|
||||
child.IgnoresContainerArea = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TabExtensions
|
||||
{
|
||||
public static Rectangle CreateRect(this Context context, int width, int height)
|
||||
{
|
||||
return new Rectangle(0, 0, context.FromPixels(width), context.FromPixels(height));
|
||||
}
|
||||
|
||||
public static void HandlePagesChanged(this ExtendedTabbedPageRenderer renderer)
|
||||
{
|
||||
renderer.SetupBottomBar();
|
||||
renderer.SetupTabItems();
|
||||
|
||||
if(renderer.Element.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureTabIndex(renderer);
|
||||
}
|
||||
|
||||
static void EnsureTabIndex(ExtendedTabbedPageRenderer renderer)
|
||||
{
|
||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
||||
|
||||
var itemIndex = menu.FindItemIndex(bottomNav.SelectedItemId);
|
||||
var pageIndex = renderer.Element.Children.IndexOf(renderer.Element.CurrentPage);
|
||||
if(pageIndex >= 0 && pageIndex != itemIndex && pageIndex < bottomNav.ItemCount)
|
||||
{
|
||||
var menuItem = menu.GetItem(pageIndex);
|
||||
bottomNav.SelectedItemId = menuItem.ItemId;
|
||||
|
||||
if(ExtendedTabbedPageRenderer.ShouldUpdateSelectedIcon && ExtendedTabbedPageRenderer.MenuItemIconSetter != null)
|
||||
{
|
||||
ExtendedTabbedPageRenderer.MenuItemIconSetter?.Invoke(menuItem, renderer.Element.CurrentPage.Icon, true);
|
||||
|
||||
if(renderer.LastSelectedIndex != pageIndex)
|
||||
{
|
||||
var lastSelectedPage = renderer.Element.Children[renderer.LastSelectedIndex];
|
||||
var lastSelectedMenuItem = menu.GetItem(renderer.LastSelectedIndex);
|
||||
ExtendedTabbedPageRenderer.MenuItemIconSetter?.Invoke(lastSelectedMenuItem, lastSelectedPage.Icon, false);
|
||||
renderer.LastSelectedIndex = pageIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwitchPage(this ExtendedTabbedPageRenderer renderer, IMenuItem item)
|
||||
{
|
||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
||||
|
||||
var index = menu.FindItemIndex(item.ItemId);
|
||||
var pageIndex = index % renderer.Element.Children.Count;
|
||||
var currentPageIndex = renderer.Element.Children.IndexOf(renderer.Element.CurrentPage);
|
||||
|
||||
if(currentPageIndex != pageIndex)
|
||||
{
|
||||
renderer.Element.CurrentPage = renderer.Element.Children[pageIndex];
|
||||
}
|
||||
}
|
||||
|
||||
public static void Layout(this ExtendedTabbedPageRenderer renderer, int width, int height)
|
||||
{
|
||||
var rootLayout = (RelativeLayout)renderer.GetChildAt(0);
|
||||
var bottomNav = (BottomNavigationViewEx)rootLayout.GetChildAt(1);
|
||||
|
||||
var Context = renderer.Context;
|
||||
|
||||
rootLayout.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
||||
MakeMeasureSpec(height, MeasureSpecMode.AtMost));
|
||||
|
||||
((IPageController)renderer.Element).ContainerArea =
|
||||
Context.CreateRect(rootLayout.MeasuredWidth, rootLayout.GetChildAt(0).MeasuredHeight);
|
||||
|
||||
rootLayout.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
||||
MakeMeasureSpec(height, MeasureSpecMode.Exactly));
|
||||
rootLayout.Layout(0, 0, rootLayout.MeasuredWidth, rootLayout.MeasuredHeight);
|
||||
|
||||
if(renderer.Element.Children.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int tabsHeight = bottomNav.MeasuredHeight;
|
||||
|
||||
var item = (ViewGroup)bottomNav.GetChildAt(0);
|
||||
item.Measure(MakeMeasureSpec(width, MeasureSpecMode.Exactly),
|
||||
MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
|
||||
|
||||
item.Layout(0, 0, width, tabsHeight);
|
||||
int item_w = width / item.ChildCount;
|
||||
for(int i = 0; i < item.ChildCount; i++)
|
||||
{
|
||||
var frame = (FrameLayout)item.GetChildAt(i);
|
||||
frame.Measure(MakeMeasureSpec(item_w, MeasureSpecMode.Exactly),
|
||||
MakeMeasureSpec(tabsHeight, MeasureSpecMode.Exactly));
|
||||
frame.Layout(i * item_w, 0, i * item_w + item_w, tabsHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetupTabItems(this ExtendedTabbedPageRenderer renderer, BottomNavigationViewEx bottomNav)
|
||||
{
|
||||
var element = renderer.Element;
|
||||
var menu = (BottomNavigationMenu)bottomNav.Menu;
|
||||
menu.ClearAll();
|
||||
|
||||
var tabsCount = Math.Min(element.Children.Count, bottomNav.MaxItemCount);
|
||||
for(int i = 0; i < tabsCount; i++)
|
||||
{
|
||||
var page = element.Children[i];
|
||||
var menuItem = menu.Add(0, i, 0, page.Title);
|
||||
var setter = ExtendedTabbedPageRenderer.MenuItemIconSetter ?? ExtendedTabbedPageRenderer.DefaultMenuItemIconSetter;
|
||||
setter.Invoke(menuItem, page.Icon, renderer.LastSelectedIndex == i);
|
||||
}
|
||||
|
||||
if(element.Children.Count > 0)
|
||||
{
|
||||
bottomNav.EnableShiftingMode(false);
|
||||
bottomNav.EnableItemShiftingMode(false);
|
||||
bottomNav.EnableAnimation(false);
|
||||
bottomNav.SetTextVisibility(false);
|
||||
bottomNav.SetBackgroundResource(Resource.Drawable.bottom_nav_bg);
|
||||
bottomNav.SetIconSize(24, 24);
|
||||
bottomNav.SetIconsMarginTop(32);
|
||||
|
||||
if(element.Children.Count > 3)
|
||||
{
|
||||
bottomNav.SetIconSizeAt(3, 29, 29);
|
||||
bottomNav.SetIconMarginTop(3, 28);
|
||||
}
|
||||
|
||||
var stateList = new global::Android.Content.Res.ColorStateList(
|
||||
new int[][] {
|
||||
new int[] { global::Android.Resource.Attribute.StateChecked },
|
||||
new int[] { global::Android.Resource.Attribute.StateEnabled}
|
||||
},
|
||||
new int[] {
|
||||
element.TintColor.ToAndroid(), // Selected
|
||||
Color.FromHex("A1A1A1").ToAndroid() // Normal
|
||||
});
|
||||
|
||||
bottomNav.ItemIconTintList = stateList;
|
||||
}
|
||||
}
|
||||
|
||||
public static BottomNavigationViewEx SetupBottomBar(this ExtendedTabbedPageRenderer renderer,
|
||||
global::Android.Widget.RelativeLayout rootLayout, BottomNavigationViewEx bottomNav, int barId)
|
||||
{
|
||||
if(bottomNav != null)
|
||||
{
|
||||
rootLayout.RemoveView(bottomNav);
|
||||
bottomNav.SetOnNavigationItemSelectedListener(null);
|
||||
}
|
||||
|
||||
var barParams = new global::Android.Widget.RelativeLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MatchParent,
|
||||
ExtendedTabbedPageRenderer.BottomBarHeight.HasValue ?
|
||||
(int)rootLayout.Context.ToPixels(ExtendedTabbedPageRenderer.BottomBarHeight.Value) :
|
||||
ViewGroup.LayoutParams.WrapContent);
|
||||
barParams.AddRule(LayoutRules.AlignParentBottom);
|
||||
bottomNav = new BottomNavigationViewEx(rootLayout.Context)
|
||||
{
|
||||
LayoutParameters = barParams,
|
||||
Id = barId
|
||||
};
|
||||
if(ExtendedTabbedPageRenderer.BackgroundColor.HasValue)
|
||||
{
|
||||
bottomNav.SetBackgroundColor(ExtendedTabbedPageRenderer.BackgroundColor.Value);
|
||||
}
|
||||
|
||||
bottomNav.SetOnNavigationItemSelectedListener(renderer);
|
||||
rootLayout.AddView(bottomNav, 1, barParams);
|
||||
|
||||
return bottomNav;
|
||||
}
|
||||
|
||||
public static void ChangePage(this ExtendedTabbedPageRenderer renderer, FrameLayout pageContainer, Page page)
|
||||
{
|
||||
renderer.Context.HideKeyboard(renderer);
|
||||
if(page == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(Platform.GetRenderer(page) == null)
|
||||
{
|
||||
Platform.SetRenderer(page, Platform.CreateRendererWithContext(page, renderer.Context));
|
||||
}
|
||||
|
||||
var pageContent = Platform.GetRenderer(page).View;
|
||||
pageContainer.AddView(pageContent);
|
||||
if(pageContainer.ChildCount > 1)
|
||||
{
|
||||
pageContainer.RemoveViewAt(0);
|
||||
}
|
||||
|
||||
EnsureTabIndex(renderer);
|
||||
}
|
||||
|
||||
public static RelativeLayout CreateRoot(this ExtendedTabbedPageRenderer renderer, int barId, int pageContainerId, out FrameLayout pageContainer)
|
||||
{
|
||||
var rootLayout = new RelativeLayout(renderer.Context)
|
||||
{
|
||||
LayoutParameters = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent),
|
||||
};
|
||||
|
||||
var pageParams = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MatchParent, ViewGroup.LayoutParams.MatchParent);
|
||||
pageParams.AddRule(LayoutRules.Above, barId);
|
||||
pageContainer = new FrameLayout(renderer.Context)
|
||||
{
|
||||
LayoutParameters = pageParams,
|
||||
Id = pageContainerId
|
||||
};
|
||||
|
||||
rootLayout.AddView(pageContainer, 0, pageParams);
|
||||
return rootLayout;
|
||||
}
|
||||
|
||||
private static int MakeMeasureSpec(int size, MeasureSpecMode mode)
|
||||
{
|
||||
return size + (int)mode;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ResourceUtils
|
||||
{
|
||||
public static int IdFromTitle(string title, Type type)
|
||||
{
|
||||
var name = Path.GetFileNameWithoutExtension(title);
|
||||
var id = GetId(type, name);
|
||||
return id;
|
||||
}
|
||||
|
||||
public static int GetId(Type type, string propertyName)
|
||||
{
|
||||
var props = type.GetFields();
|
||||
var prop = props.Select(p => p).FirstOrDefault(p => p.Name == propertyName);
|
||||
if(prop != null)
|
||||
{
|
||||
return (int)prop.GetValue(type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
src/Android/Controls/ExtendedTableViewRenderer.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using Android.Widget;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Content;
|
||||
using AView = Android.Views.View;
|
||||
using AListView = Android.Widget.ListView;
|
||||
using Android.Views;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedTableView), typeof(ExtendedTableViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedTableViewRenderer : TableViewRenderer
|
||||
{
|
||||
public ExtendedTableViewRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<TableView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
Control.Divider = null;
|
||||
Control.DividerHeight = 0;
|
||||
|
||||
if(e.NewElement is ExtendedTableView tableView)
|
||||
{
|
||||
if(tableView.BottomPadding > 0)
|
||||
{
|
||||
Control.SetPadding(0, 0, 0, tableView.BottomPadding);
|
||||
Control.SetClipToPadding(false);
|
||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override TableViewModelRenderer GetModelRenderer(AListView listView, TableView view)
|
||||
{
|
||||
return new CustomTableViewModelRenderer(Context, listView, view);
|
||||
}
|
||||
|
||||
public override SizeRequest GetDesiredSize(int widthConstraint, int heightConstraint)
|
||||
{
|
||||
var baseSize = base.GetDesiredSize(widthConstraint, heightConstraint);
|
||||
var height = ComputeHeight(Control, Convert.ToInt32(baseSize.Request.Width));
|
||||
return new SizeRequest(new Size(baseSize.Request.Width, height));
|
||||
}
|
||||
|
||||
private int ComputeHeight(AListView listView, int width)
|
||||
{
|
||||
var element = Element as ExtendedTableView;
|
||||
|
||||
var adapter = listView.Adapter;
|
||||
var totalHeight = listView.PaddingTop + listView.PaddingBottom;
|
||||
var desiredWidth = MeasureSpec.MakeMeasureSpec(width, MeasureSpecMode.AtMost);
|
||||
for(var i = 0; i < adapter.Count; i++)
|
||||
{
|
||||
if(i == 0 && (element?.NoHeader ?? false))
|
||||
{
|
||||
totalHeight += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
var view = adapter.GetView(i, null, listView);
|
||||
view.LayoutParameters = new LayoutParams(LayoutParams.WrapContent, LayoutParams.WrapContent);
|
||||
view.Measure(desiredWidth, MeasureSpec.MakeMeasureSpec(0, MeasureSpecMode.Unspecified));
|
||||
totalHeight += view.MeasuredHeight;
|
||||
}
|
||||
|
||||
return totalHeight + (listView.DividerHeight * (adapter.Count - 1));
|
||||
}
|
||||
|
||||
private class CustomTableViewModelRenderer : TableViewModelRenderer
|
||||
{
|
||||
private readonly ExtendedTableView _view;
|
||||
private readonly AListView _listView;
|
||||
|
||||
public CustomTableViewModelRenderer(Context context, AListView listView, TableView view)
|
||||
: base(context, listView, view)
|
||||
{
|
||||
_view = view as ExtendedTableView;
|
||||
_listView = listView;
|
||||
}
|
||||
|
||||
private ITableViewController Controller => _view;
|
||||
|
||||
// ref http://bit.ly/2b9cjnQ
|
||||
public override AView GetView(int position, AView convertView, ViewGroup parent)
|
||||
{
|
||||
var baseView = base.GetView(position, convertView, parent);
|
||||
var layout = baseView as LinearLayout;
|
||||
if(layout == null)
|
||||
{
|
||||
return baseView;
|
||||
}
|
||||
|
||||
bool isHeader, nextIsHeader;
|
||||
var cell = GetCellForPosition(position, out isHeader, out nextIsHeader);
|
||||
if(layout.ChildCount > 0)
|
||||
{
|
||||
layout.RemoveViewAt(0);
|
||||
var cellView = CellFactory.GetCell(cell, convertView, parent, Context, _view);
|
||||
layout.AddView(cellView, 0);
|
||||
}
|
||||
|
||||
if(isHeader)
|
||||
{
|
||||
var textCell = layout.GetChildAt(0) as BaseCellView;
|
||||
if(textCell != null)
|
||||
{
|
||||
if(position == 0 && _view.NoHeader)
|
||||
{
|
||||
textCell.Visibility = ViewStates.Gone;
|
||||
}
|
||||
else
|
||||
{
|
||||
textCell.MainText = textCell.MainText?.ToUpperInvariant();
|
||||
textCell.SetMainTextColor(Color.FromHex("777777"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var bline = layout.GetChildAt(1);
|
||||
if(bline != null)
|
||||
{
|
||||
bline.SetBackgroundColor(_view.SeparatorColor.ToAndroid());
|
||||
}
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
// Copy/pasted from Xamarin source. Invoke via reflection instead maybe?
|
||||
private Cell GetCellForPosition(int position, out bool isHeader, out bool nextIsHeader)
|
||||
{
|
||||
isHeader = false;
|
||||
nextIsHeader = false;
|
||||
|
||||
var model = Controller.Model;
|
||||
var sectionCount = model.GetSectionCount();
|
||||
|
||||
for(var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++)
|
||||
{
|
||||
var size = model.GetRowCount(sectionIndex) + 1;
|
||||
if(position == 0)
|
||||
{
|
||||
isHeader = true;
|
||||
nextIsHeader = size == 0 && sectionIndex < sectionCount - 1;
|
||||
|
||||
var header = model.GetHeaderCell(sectionIndex);
|
||||
Cell resultCell = null;
|
||||
if(header != null)
|
||||
{
|
||||
resultCell = header;
|
||||
}
|
||||
|
||||
if(resultCell == null)
|
||||
{
|
||||
resultCell = new TextCell { Text = model.GetSectionTitle(sectionIndex) };
|
||||
}
|
||||
|
||||
resultCell.Parent = _view;
|
||||
return resultCell;
|
||||
}
|
||||
|
||||
if(position < size)
|
||||
{
|
||||
nextIsHeader = position == size - 1;
|
||||
return (Cell)model.GetItem(sectionIndex, position - 1);
|
||||
}
|
||||
|
||||
position -= size;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using AView = Android.Views.View;
|
||||
using Android.Widget;
|
||||
using Android.Text;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedTextCell), typeof(ExtendedTextCellRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
@@ -22,7 +23,14 @@ namespace Bit.Android.Controls
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
if(extendedCell.BackgroundColor != Color.White)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||
}
|
||||
|
||||
if(extendedCell.ShowDisclousure)
|
||||
{
|
||||
@@ -38,6 +46,31 @@ namespace Bit.Android.Controls
|
||||
image.SetPadding(10, 10, 30, 10);
|
||||
View.SetAccessoryView(image);
|
||||
}
|
||||
|
||||
if(View.ChildCount > 1)
|
||||
{
|
||||
var layout = View.GetChildAt(1) as LinearLayout;
|
||||
if(layout != null)
|
||||
{
|
||||
if(layout.ChildCount > 0)
|
||||
{
|
||||
var textView = layout.GetChildAt(0) as TextView;
|
||||
if(textView != null)
|
||||
{
|
||||
textView.TextSize = (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label));
|
||||
}
|
||||
}
|
||||
|
||||
if(layout.ChildCount > 1)
|
||||
{
|
||||
var detailView = layout.GetChildAt(1) as TextView;
|
||||
if(detailView != null)
|
||||
{
|
||||
UpdateLineBreakMode(detailView, extendedCell.DetailLineBreakMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return View;
|
||||
@@ -57,6 +90,44 @@ namespace Bit.Android.Controls
|
||||
// TODO: other properties
|
||||
}
|
||||
|
||||
private void UpdateLineBreakMode(TextView view, LineBreakMode lineBreakMode)
|
||||
{
|
||||
if(view == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
switch(lineBreakMode)
|
||||
{
|
||||
case LineBreakMode.NoWrap:
|
||||
view.SetSingleLine(true);
|
||||
view.Ellipsize = null;
|
||||
break;
|
||||
case LineBreakMode.WordWrap:
|
||||
view.SetSingleLine(false);
|
||||
view.Ellipsize = null;
|
||||
view.SetMaxLines(100);
|
||||
break;
|
||||
case LineBreakMode.CharacterWrap:
|
||||
view.SetSingleLine(false);
|
||||
view.Ellipsize = null;
|
||||
view.SetMaxLines(100);
|
||||
break;
|
||||
case LineBreakMode.HeadTruncation:
|
||||
view.SetSingleLine(true);
|
||||
view.Ellipsize = TextUtils.TruncateAt.Start;
|
||||
break;
|
||||
case LineBreakMode.TailTruncation:
|
||||
view.SetSingleLine(true);
|
||||
view.Ellipsize = TextUtils.TruncateAt.End;
|
||||
break;
|
||||
case LineBreakMode.MiddleTruncation:
|
||||
view.SetSingleLine(true);
|
||||
view.Ellipsize = TextUtils.TruncateAt.Middle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private class DisclosureImage : ImageView
|
||||
{
|
||||
private ExtendedTextCell _cell;
|
||||
|
||||
@@ -21,7 +21,14 @@ namespace Bit.Android.Controls
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
if(extendedCell.BackgroundColor != Color.White)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||
}
|
||||
}
|
||||
|
||||
return View;
|
||||
|
||||
78
src/Android/Controls/HybridWebViewRenderer.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Webkit;
|
||||
using AWebkit = Android.Webkit;
|
||||
using Java.Interop;
|
||||
using Android.Content;
|
||||
using Plugin.CurrentActivity;
|
||||
|
||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
||||
{
|
||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||
|
||||
public HybridWebViewRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(Control == null)
|
||||
{
|
||||
var webView = new AWebkit.WebView(CrossCurrentActivity.Current.Activity);
|
||||
webView.Settings.JavaScriptEnabled = true;
|
||||
SetNativeControl(webView);
|
||||
}
|
||||
|
||||
if(e.OldElement != null)
|
||||
{
|
||||
Control.RemoveJavascriptInterface("jsBridge");
|
||||
var hybridWebView = e.OldElement as HybridWebView;
|
||||
hybridWebView.Cleanup();
|
||||
}
|
||||
|
||||
if(e.NewElement != null)
|
||||
{
|
||||
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
|
||||
Control.LoadUrl(Element.Uri);
|
||||
InjectJS(JSFunction);
|
||||
}
|
||||
}
|
||||
|
||||
private void InjectJS(string script)
|
||||
{
|
||||
if(Control != null)
|
||||
{
|
||||
Control.LoadUrl(string.Format("javascript: {0}", script));
|
||||
}
|
||||
}
|
||||
|
||||
public class JSBridge : Java.Lang.Object
|
||||
{
|
||||
private readonly WeakReference<HybridWebViewRenderer> _hybridWebViewRenderer;
|
||||
|
||||
public JSBridge(HybridWebViewRenderer hybridRenderer)
|
||||
{
|
||||
_hybridWebViewRenderer = new WeakReference<HybridWebViewRenderer>(hybridRenderer);
|
||||
}
|
||||
|
||||
[JavascriptInterface]
|
||||
[Export("invokeAction")]
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
HybridWebViewRenderer hybridRenderer;
|
||||
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
||||
{
|
||||
hybridRenderer.Element.InvokeAction(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Android/FirebaseInstanceIdService.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
#if !FDROID
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App;
|
||||
using Bit.App.Abstractions;
|
||||
using Firebase.Iid;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
||||
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
||||
{
|
||||
public override void OnTokenRefresh()
|
||||
{
|
||||
var settings = Resolver.Resolve<ISettings>();
|
||||
settings.AddOrUpdateValue(Constants.PushRegisteredToken, FirebaseInstanceId.Instance.Token);
|
||||
Resolver.Resolve<IPushNotificationService>()?.Register();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
44
src/Android/FirebaseMessagingService.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
#if !FDROID
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Firebase.Messaging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
||||
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
||||
{
|
||||
public override void OnMessageReceived(RemoteMessage message)
|
||||
{
|
||||
if(message?.Data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||
if(data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var obj = JObject.Parse(data);
|
||||
var listener = Resolver.Resolve<IPushNotificationListener>();
|
||||
listener.OnMessage(obj, Device.Android);
|
||||
}
|
||||
catch(JsonReaderException ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
56
src/Android/HockeyAppCrashManagerListener.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
#if !FDROID
|
||||
using HockeyApp.Android;
|
||||
using Bit.App.Abstractions;
|
||||
using Newtonsoft.Json;
|
||||
using Android.Runtime;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
public class HockeyAppCrashManagerListener : CrashManagerListener
|
||||
{
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IAuthService _authService;
|
||||
|
||||
public HockeyAppCrashManagerListener()
|
||||
{ }
|
||||
|
||||
public HockeyAppCrashManagerListener(System.IntPtr javaRef, JniHandleOwnership transfer)
|
||||
: base(javaRef, transfer)
|
||||
{ }
|
||||
|
||||
public HockeyAppCrashManagerListener(
|
||||
IAppIdService appIdService,
|
||||
IAuthService authService)
|
||||
{
|
||||
_appIdService = appIdService;
|
||||
_authService = authService;
|
||||
}
|
||||
|
||||
public override string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
if(_appIdService != null && _authService != null)
|
||||
{
|
||||
var log = new
|
||||
{
|
||||
AppId = _appIdService.AppId,
|
||||
UserId = _authService.UserId
|
||||
};
|
||||
|
||||
return JsonConvert.SerializeObject(log, Formatting.Indented);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ShouldAutoUploadCrashes()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Android.AccessibilityServices;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Views.Accessibility;
|
||||
using Android.Widget;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Service(Permission = "android.permission.BIND_ACCESSIBILITY_SERVICE", Label = "bitwarden")]
|
||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||
public class LoginService : AccessibilityService
|
||||
{
|
||||
private View mView;
|
||||
|
||||
private WindowManagerLayoutParams mParams;
|
||||
private IWindowManager mWindowManager;
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
var eventType = e.EventType;
|
||||
switch(eventType)
|
||||
{
|
||||
case EventTypes.ViewTextSelectionChanged:
|
||||
if(e.Source.Password && string.IsNullOrWhiteSpace(e.Source.Text))
|
||||
{
|
||||
MakeWindow();
|
||||
var bundle = new Bundle();
|
||||
bundle.PutCharSequence(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, "mypassword");
|
||||
e.Source.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInterrupt()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void MakeWindow()
|
||||
{
|
||||
mView = new MyLoadView(this);
|
||||
|
||||
mParams = new WindowManagerLayoutParams(
|
||||
WindowManagerTypes.SystemOverlay,
|
||||
WindowManagerFlags.WatchOutsideTouch,
|
||||
Format.Translucent);
|
||||
|
||||
mParams.Gravity = GravityFlags.Top | GravityFlags.Right;
|
||||
mParams.Title = "Test window";
|
||||
|
||||
mWindowManager = GetSystemService(WindowService).JavaCast<IWindowManager>();
|
||||
mWindowManager.AddView(mView, mParams);
|
||||
}
|
||||
|
||||
public class MyLoadView : View
|
||||
{
|
||||
private Paint mPaint;
|
||||
|
||||
public MyLoadView(Context context)
|
||||
: base(context)
|
||||
{
|
||||
mPaint = new Paint();
|
||||
mPaint.TextSize = 50;
|
||||
mPaint.SetARGB(200, 200, 200, 200);
|
||||
}
|
||||
|
||||
protected override void OnDraw(Canvas canvas)
|
||||
{
|
||||
base.OnDraw(canvas);
|
||||
canvas.DrawText("test test test", 0, 100, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,108 @@
|
||||
using System;
|
||||
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Android.OS;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Fingerprint.Abstractions;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.Abstractions;
|
||||
using Acr.UserDialogs;
|
||||
using PushNotification.Plugin.Abstractions;
|
||||
using Android.Content;
|
||||
using System.Reflection;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Xamarin.Forms;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App;
|
||||
using Android.Nfc;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Activity(Label = "bitwarden", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
|
||||
[Activity(ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, Exported = false)]
|
||||
public class MainActivity : FormsAppCompatActivity
|
||||
{
|
||||
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
|
||||
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IDeviceInfoService _deviceInfoService;
|
||||
private IAppSettingsService _appSettingsService;
|
||||
private ISettings _settings;
|
||||
private AppOptions _appOptions;
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
MainApplication.SetIoc(Application);
|
||||
}
|
||||
|
||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
||||
StrictMode.SetThreadPolicy(policy);
|
||||
|
||||
ToolbarResource = Resource.Layout.toolbar;
|
||||
TabLayoutResource = Resource.Layout.tabs;
|
||||
|
||||
base.OnCreate(bundle);
|
||||
|
||||
// workaround for app compat bug
|
||||
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
||||
Task.Delay(10).Wait();
|
||||
|
||||
Console.WriteLine("A OnCreate");
|
||||
if(!App.Utilities.Helpers.InDebugMode())
|
||||
{
|
||||
Window.AddFlags(WindowManagerFlags.Secure);
|
||||
}
|
||||
|
||||
global::Xamarin.Forms.Forms.Init(this, bundle);
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
var authService = Resolver.Resolve<IAuthService>();
|
||||
|
||||
#if !FDROID
|
||||
HockeyApp.Android.CrashManager.Register(this, HockeyAppId,
|
||||
new HockeyAppCrashManagerListener(appIdService, authService));
|
||||
#endif
|
||||
|
||||
Forms.Init(this, bundle);
|
||||
|
||||
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
|
||||
.SetValue(null, Color.FromHex("d2d6de"));
|
||||
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
|
||||
_appSettingsService = Resolver.Resolve<IAppSettingsService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
_appOptions = GetOptions();
|
||||
LoadApplication(new App.App(
|
||||
_appOptions,
|
||||
Resolver.Resolve<IAuthService>(),
|
||||
Resolver.Resolve<IConnectivity>(),
|
||||
Resolver.Resolve<IUserDialogs>(),
|
||||
Resolver.Resolve<IDatabaseService>(),
|
||||
Resolver.Resolve<ISyncService>(),
|
||||
Resolver.Resolve<IFingerprint>(),
|
||||
Resolver.Resolve<ISettings>(),
|
||||
Resolver.Resolve<IPushNotification>(),
|
||||
_settings,
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<IGoogleAnalyticsService>()));
|
||||
Resolver.Resolve<ILocalizeService>(),
|
||||
Resolver.Resolve<IAppInfoService>(),
|
||||
_appSettingsService,
|
||||
_deviceActionService));
|
||||
|
||||
if(_appOptions?.Uri == null)
|
||||
{
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application, bool>(Xamarin.Forms.Application.Current,
|
||||
"ListenYubiKeyOTP", (sender, listen) => ListenYubiKey(listen));
|
||||
|
||||
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current,
|
||||
"FinishMainActivity", (sender) => Finish());
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPause()
|
||||
{
|
||||
Console.WriteLine("A OnPause");
|
||||
base.OnPause();
|
||||
ListenYubiKey(false);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
@@ -71,8 +131,161 @@ namespace Bit.Android
|
||||
|
||||
protected override void OnResume()
|
||||
{
|
||||
Console.WriteLine("A OnResume");
|
||||
base.OnResume();
|
||||
Console.WriteLine("A OnResume");
|
||||
|
||||
// workaround for app compat bug
|
||||
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
|
||||
Task.Delay(10).Wait();
|
||||
|
||||
if(_deviceInfoService.NfcEnabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(e);
|
||||
}
|
||||
}
|
||||
|
||||
if(_appSettingsService.Locked)
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "Resumed", false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
Console.WriteLine("A OnNewIntent");
|
||||
ParseYubiKey(intent.DataString);
|
||||
}
|
||||
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
{
|
||||
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
{
|
||||
if(grantResults.Any(r => r != Permission.Granted))
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
|
||||
}
|
||||
await _deviceActionService.SelectFileAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
if(requestCode == Constants.SelectFileRequestCode && resultCode == Result.Ok)
|
||||
{
|
||||
global::Android.Net.Uri uri = null;
|
||||
string fileName = null;
|
||||
if(data != null && data.Data != null)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = Utilities.GetFileName(ApplicationContext, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// camera
|
||||
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||
uri = global::Android.Net.Uri.FromFile(file);
|
||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||
}
|
||||
|
||||
if(uri == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||
using(var memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
|
||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ListenYubiKey(bool listen)
|
||||
{
|
||||
if(!_deviceInfoService.NfcEnabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var adapter = NfcAdapter.GetDefaultAdapter(this);
|
||||
if(listen)
|
||||
{
|
||||
var intent = new Intent(this, Class);
|
||||
intent.AddFlags(ActivityFlags.SingleTop);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||
|
||||
// register for all NDEF tags starting with http och https
|
||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||
ndef.AddDataScheme("http");
|
||||
ndef.AddDataScheme("https");
|
||||
var filters = new IntentFilter[] { ndef };
|
||||
|
||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
adapter.DisableForegroundDispatch(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseYubiKey(string data)
|
||||
{
|
||||
if(data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var otpMatch = _otpPattern.Matcher(data);
|
||||
if(otpMatch.Matches())
|
||||
{
|
||||
var otp = otpMatch.Group(1);
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "GotYubiKeyOTP", otp);
|
||||
}
|
||||
}
|
||||
|
||||
private AppOptions GetOptions()
|
||||
{
|
||||
var options = new AppOptions
|
||||
{
|
||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
|
||||
};
|
||||
|
||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
||||
if(fillType > 0)
|
||||
{
|
||||
options.FillType = (CipherType)fillType;
|
||||
}
|
||||
|
||||
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
|
||||
{
|
||||
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
|
||||
options.SaveName = Intent.GetStringExtra("autofillFrameworkName");
|
||||
options.SaveUsername = Intent.GetStringExtra("autofillFrameworkUsername");
|
||||
options.SavePassword = Intent.GetStringExtra("autofillFrameworkPassword");
|
||||
options.SaveCardName = Intent.GetStringExtra("autofillFrameworkCardName");
|
||||
options.SaveCardNumber = Intent.GetStringExtra("autofillFrameworkCardNumber");
|
||||
options.SaveCardExpMonth = Intent.GetStringExtra("autofillFrameworkCardExpMonth");
|
||||
options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear");
|
||||
options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode");
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Acr.UserDialogs;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
@@ -8,41 +7,58 @@ using Bit.Android.Services;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Repositories;
|
||||
using Bit.App.Services;
|
||||
using Microsoft.Practices.Unity;
|
||||
using Plugin.Connectivity;
|
||||
using Plugin.CurrentActivity;
|
||||
using Plugin.DeviceInfo;
|
||||
using Plugin.Fingerprint;
|
||||
using Plugin.Settings;
|
||||
using PushNotification.Plugin;
|
||||
using PushNotification.Plugin.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using XLabs.Ioc.Unity;
|
||||
using System.Threading.Tasks;
|
||||
using FFImageLoading.Forms.Droid;
|
||||
using XLabs.Ioc.SimpleInjectorContainer;
|
||||
using SimpleInjector;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Application]
|
||||
#if DEBUG
|
||||
[Application(Debuggable = true)]
|
||||
#else
|
||||
[Application(Debuggable = false)]
|
||||
#endif
|
||||
public class MainApplication : Application, Application.IActivityLifecycleCallbacks
|
||||
{
|
||||
private const string FirstLaunchKey = "firstLaunch";
|
||||
private const string LastVersionCodeKey = "lastVersionCode";
|
||||
|
||||
public static Context AppContext;
|
||||
|
||||
public MainApplication(IntPtr handle, JniHandleOwnership transer)
|
||||
: base(handle, transer)
|
||||
{
|
||||
//AndroidEnvironment.UnhandledExceptionRaiser += AndroidEnvironment_UnhandledExceptionRaiser;
|
||||
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
SetIoc();
|
||||
SetIoc(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
|
||||
{
|
||||
var message = Utilities.AppendExceptionToMessage("", e.Exception);
|
||||
//Utilities.SaveCrashFile(message, true);
|
||||
Utilities.SendCrashEmail(message, false);
|
||||
}
|
||||
|
||||
public override void OnCreate()
|
||||
{
|
||||
base.OnCreate();
|
||||
|
||||
// workaround for app compat bug
|
||||
// ref https://forums.xamarin.com/discussion/62414/app-resuming-results-in-crash-with-formsappcompatactivity
|
||||
Task.Delay(10).Wait();
|
||||
|
||||
RegisterActivityLifecycleCallbacks(this);
|
||||
AppContext = ApplicationContext;
|
||||
StartPushService();
|
||||
Resolver.Resolve<IPushNotification>().Unregister();
|
||||
Resolver.Resolve<IPushNotification>().Register();
|
||||
}
|
||||
|
||||
public override void OnTerminate()
|
||||
@@ -82,77 +98,81 @@ namespace Bit.Android
|
||||
{
|
||||
}
|
||||
|
||||
public static void StartPushService()
|
||||
public static void SetIoc(Application application)
|
||||
{
|
||||
AppContext.StartService(new Intent(AppContext, typeof(PushNotificationService)));
|
||||
if(global::Android.OS.Build.VERSION.SdkInt >= global::Android.OS.BuildVersionCodes.Kitkat)
|
||||
{
|
||||
PendingIntent pintent = PendingIntent.GetService(AppContext, 0, new Intent(AppContext, typeof(PushNotificationService)), 0);
|
||||
AlarmManager alarm = (AlarmManager)AppContext.GetSystemService(AlarmService);
|
||||
alarm.Cancel(pintent);
|
||||
}
|
||||
}
|
||||
|
||||
public static void StopPushService()
|
||||
{
|
||||
AppContext.StopService(new Intent(AppContext, typeof(PushNotificationService)));
|
||||
if(global::Android.OS.Build.VERSION.SdkInt >= global::Android.OS.BuildVersionCodes.Kitkat)
|
||||
{
|
||||
PendingIntent pintent = PendingIntent.GetService(AppContext, 0, new Intent(AppContext, typeof(PushNotificationService)), 0);
|
||||
AlarmManager alarm = (AlarmManager)AppContext.GetSystemService(AlarmService);
|
||||
alarm.Cancel(pintent);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetIoc()
|
||||
{
|
||||
var container = new UnityContainer();
|
||||
|
||||
container
|
||||
// Android Stuff
|
||||
.RegisterInstance(ApplicationContext)
|
||||
.RegisterInstance<Application>(this)
|
||||
// Services
|
||||
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISecureStorageService, KeyStoreStorageService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ICryptoService, CryptoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IKeyDerivationService, BouncyCastleKeyDerivationService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAppIdService, AppIdService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IPasswordGenerationService, PasswordGenerationService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IReflectionService, ReflectionService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ILockService, LockService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAppInfoService, AppInfoService>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IGoogleAnalyticsService, GoogleAnalyticsService>(new ContainerControlledLifetimeManager())
|
||||
// Repositories
|
||||
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IDeviceApiRepository, DeviceApiRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<IAccountsApiRepository, AccountsApiRepository>(new ContainerControlledLifetimeManager())
|
||||
.RegisterType<ICipherApiRepository, CipherApiRepository>(new ContainerControlledLifetimeManager())
|
||||
// Other
|
||||
.RegisterInstance(CrossDeviceInfo.Current, new ContainerControlledLifetimeManager())
|
||||
.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
|
||||
.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())
|
||||
.RegisterInstance(UserDialogs.Instance, new ContainerControlledLifetimeManager())
|
||||
.RegisterInstance(CrossFingerprint.Current, new ContainerControlledLifetimeManager());
|
||||
|
||||
CrossPushNotification.Initialize(container.Resolve<IPushNotificationListener>(), "SECRET_SENDER_ID");
|
||||
container.RegisterInstance(CrossPushNotification.Current, new ContainerControlledLifetimeManager());
|
||||
|
||||
Resolver.SetResolver(new UnityResolver(container));
|
||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||
CachedImageRenderer.Init(true);
|
||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
|
||||
UserDialogs.Init(this);
|
||||
//var container = new UnityContainer();
|
||||
var container = new Container();
|
||||
|
||||
// Android Stuff
|
||||
container.RegisterSingleton(application.ApplicationContext);
|
||||
container.RegisterSingleton<Application>(application);
|
||||
|
||||
// Services
|
||||
container.RegisterSingleton<IDatabaseService, DatabaseService>();
|
||||
container.RegisterSingleton<ISqlService, SqlService>();
|
||||
container.RegisterSingleton<ISecureStorageService, AndroidKeyStoreStorageService>();
|
||||
container.RegisterSingleton<ICryptoService, CryptoService>();
|
||||
container.RegisterSingleton<IKeyDerivationService, BouncyCastleKeyDerivationService>();
|
||||
container.RegisterSingleton<IAuthService, AuthService>();
|
||||
container.RegisterSingleton<IFolderService, FolderService>();
|
||||
container.RegisterSingleton<ICollectionService, CollectionService>();
|
||||
container.RegisterSingleton<ICipherService, CipherService>();
|
||||
container.RegisterSingleton<ISyncService, SyncService>();
|
||||
container.RegisterSingleton<IDeviceActionService, DeviceActionService>();
|
||||
container.RegisterSingleton<IAppIdService, AppIdService>();
|
||||
container.RegisterSingleton<IPasswordGenerationService, PasswordGenerationService>();
|
||||
container.RegisterSingleton<ILockService, LockService>();
|
||||
container.RegisterSingleton<IAppInfoService, AppInfoService>();
|
||||
#if FDROID
|
||||
container.RegisterSingleton<IGoogleAnalyticsService, NoopGoogleAnalyticsService>();
|
||||
#else
|
||||
container.RegisterSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>();
|
||||
#endif
|
||||
container.RegisterSingleton<IDeviceInfoService, DeviceInfoService>();
|
||||
container.RegisterSingleton<ILocalizeService, LocalizeService>();
|
||||
container.RegisterSingleton<ILogService, LogService>();
|
||||
container.RegisterSingleton<IHttpService, HttpService>();
|
||||
container.RegisterSingleton<ITokenService, TokenService>();
|
||||
container.RegisterSingleton<ISettingsService, SettingsService>();
|
||||
container.RegisterSingleton<IAppSettingsService, AppSettingsService>();
|
||||
|
||||
// Repositories
|
||||
container.RegisterSingleton<IFolderRepository, FolderRepository>();
|
||||
container.RegisterSingleton<IFolderApiRepository, FolderApiRepository>();
|
||||
container.RegisterSingleton<ICipherRepository, CipherRepository>();
|
||||
container.RegisterSingleton<IAttachmentRepository, AttachmentRepository>();
|
||||
container.RegisterSingleton<IConnectApiRepository, ConnectApiRepository>();
|
||||
container.RegisterSingleton<IDeviceApiRepository, DeviceApiRepository>();
|
||||
container.RegisterSingleton<IAccountsApiRepository, AccountsApiRepository>();
|
||||
container.RegisterSingleton<ICipherApiRepository, CipherApiRepository>();
|
||||
container.RegisterSingleton<ISettingsRepository, SettingsRepository>();
|
||||
container.RegisterSingleton<ISettingsApiRepository, SettingsApiRepository>();
|
||||
container.RegisterSingleton<ITwoFactorApiRepository, TwoFactorApiRepository>();
|
||||
container.RegisterSingleton<ISyncApiRepository, SyncApiRepository>();
|
||||
container.RegisterSingleton<ICollectionRepository, CollectionRepository>();
|
||||
container.RegisterSingleton<ICipherCollectionRepository, CipherCollectionRepository>();
|
||||
|
||||
// Other
|
||||
container.RegisterSingleton(CrossSettings.Current);
|
||||
container.RegisterSingleton(CrossConnectivity.Current);
|
||||
container.RegisterSingleton(CrossFingerprint.Current);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
container.RegisterSingleton<IPushNotificationListener, NoopPushNotificationListener>();
|
||||
container.RegisterSingleton<IPushNotificationService, NoopPushNotificationService>();
|
||||
#else
|
||||
container.RegisterSingleton<IPushNotificationListener, PushNotificationListener>();
|
||||
container.RegisterSingleton<IPushNotificationService, AndroidPushNotificationService>();
|
||||
#endif
|
||||
|
||||
container.Verify();
|
||||
Resolver.SetResolver(new SimpleInjectorResolver(container));
|
||||
}
|
||||
}
|
||||
}
|
||||
58
src/Android/MyVaultTileService.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Service.QuickSettings;
|
||||
using Java.Lang;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[Service(Permission = global::Android.Manifest.Permission.BindQuickSettingsTile,
|
||||
Label = "@string/MyVault", Icon = "@drawable/shield")]
|
||||
[IntentFilter(new string[] { ActionQsTile })]
|
||||
public class MyVaultTileService : TileService
|
||||
{
|
||||
public override void OnTileAdded()
|
||||
{
|
||||
base.OnTileAdded();
|
||||
}
|
||||
|
||||
public override void OnStartListening()
|
||||
{
|
||||
base.OnStartListening();
|
||||
}
|
||||
|
||||
public override void OnStopListening()
|
||||
{
|
||||
base.OnStopListening();
|
||||
}
|
||||
|
||||
public override void OnTileRemoved()
|
||||
{
|
||||
base.OnTileRemoved();
|
||||
}
|
||||
|
||||
public override void OnClick()
|
||||
{
|
||||
base.OnClick();
|
||||
|
||||
if(IsLocked)
|
||||
{
|
||||
UnlockAndRun(new Runnable(() =>
|
||||
{
|
||||
LaunchMyVault();
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
LaunchMyVault();
|
||||
}
|
||||
}
|
||||
|
||||
private void LaunchMyVault()
|
||||
{
|
||||
var intent = new Intent(this, typeof(SplashActivity));
|
||||
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
|
||||
intent.PutExtra("myVaultTile", true);
|
||||
StartActivityAndCollapse(intent);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
src/Android/Naxam.Ittianyu.BottomNavExtension.dll
Normal file
23
src/Android/PackageReplacedReceiver.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using System;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = false)]
|
||||
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
||||
public class PackageReplacedReceiver : BroadcastReceiver
|
||||
{
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
Console.WriteLine("Bitwarden App Updated!!");
|
||||
Helpers.PerformUpdateTasks(Resolver.Resolve<ISettings>(),
|
||||
Resolver.Resolve<IAppInfoService>(), Resolver.Resolve<IDatabaseService>(),
|
||||
Resolver.Resolve<ISyncService>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.bitwarden.vault" android:versionCode="1" android:versionName="0.0.2" android:installLocation="auto">
|
||||
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="23" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
|
||||
<uses-permission android:name="com.bitwarden.vault.permission.C2D_MESSAGE" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<application android:label="bitwarden" android:theme="@style/BitwardenTheme"></application>
|
||||
</manifest>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.17.0" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.NFC" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||
|
||||
<application android:label="Bitwarden" android:theme="@style/BitwardenTheme" android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
|
||||
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
|
||||
android:exported="false" />
|
||||
<receiver android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
|
||||
android:exported="true" android:permission="com.google.android.c2dm.permission.SEND">
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
|
||||
<action android:name="com.google.android.c2dm.intent.REGISTRATION" />
|
||||
<category android:name="${applicationId}" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name="net.hockeyapp.android.UpdateActivity" android:exported="false" />
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -6,11 +6,11 @@ using Android.App;
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Bit.Android")]
|
||||
[assembly: AssemblyTitle("BitwardenAndroid")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("bitwarden")]
|
||||
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||
[assembly: AssemblyProduct("Bitwarden")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
@@ -28,7 +28,3 @@ using Android.App;
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
||||
// Add some common permissions, these can be removed if not needed
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
|
||||
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]
|
||||
|
||||
10244
src/Android/Resources/Resource.Designer.cs
generated
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_step1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
src/Android/Resources/drawable-hdpi/accessibility_step2.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
src/Android/Resources/drawable-hdpi/android.png
Normal file
|
After Width: | Height: | Size: 530 B |
BIN
src/Android/Resources/drawable-hdpi/apple.png
Normal file
|
After Width: | Height: | Size: 625 B |
BIN
src/Android/Resources/drawable-hdpi/autofill_enable.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
src/Android/Resources/drawable-hdpi/autofill_use.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 62 KiB |
BIN
src/Android/Resources/drawable-hdpi/camera.png
Normal file
|
After Width: | Height: | Size: 664 B |
BIN
src/Android/Resources/drawable-hdpi/card.png
Normal file
|
After Width: | Height: | Size: 180 B |
BIN
src/Android/Resources/drawable-hdpi/clipboard.png
Normal file
|
After Width: | Height: | Size: 426 B |
BIN
src/Android/Resources/drawable-hdpi/cloudup.png
Normal file
|
After Width: | Height: | Size: 977 B |
BIN
src/Android/Resources/drawable-hdpi/cog.png
Normal file
|
After Width: | Height: | Size: 883 B |
BIN
src/Android/Resources/drawable-hdpi/cog_alt.png
Normal file
|
After Width: | Height: | Size: 738 B |
BIN
src/Android/Resources/drawable-hdpi/cogs.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
src/Android/Resources/drawable-hdpi/cube.png
Normal file
|
After Width: | Height: | Size: 738 B |
BIN
src/Android/Resources/drawable-hdpi/download.png
Normal file
|
After Width: | Height: | Size: 467 B |
BIN
src/Android/Resources/drawable-hdpi/envelope.png
Normal file
|
After Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 607 B After Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 659 B After Width: | Height: | Size: 937 B |
|
Before Width: | Height: | Size: 378 B |
BIN
src/Android/Resources/drawable-hdpi/fa_lock.png
Normal file
|
After Width: | Height: | Size: 569 B |
BIN
src/Android/Resources/drawable-hdpi/fingerprint.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
src/Android/Resources/drawable-hdpi/folder.png
Normal file
|
After Width: | Height: | Size: 431 B |
BIN
src/Android/Resources/drawable-hdpi/folder_o.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
src/Android/Resources/drawable-hdpi/globe.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
src/Android/Resources/drawable-hdpi/id.png
Normal file
|
After Width: | Height: | Size: 487 B |
BIN
src/Android/Resources/drawable-hdpi/ion_chevron_right.png
Normal file
|
After Width: | Height: | Size: 513 B |
BIN
src/Android/Resources/drawable-hdpi/launch.png
Normal file
|
After Width: | Height: | Size: 686 B |
BIN
src/Android/Resources/drawable-hdpi/lightbulb.png
Normal file
|
After Width: | Height: | Size: 634 B |
BIN
src/Android/Resources/drawable-hdpi/lock.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
src/Android/Resources/drawable-hdpi/login.png
Normal file
|
After Width: | Height: | Size: 904 B |
BIN
src/Android/Resources/drawable-hdpi/logo.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 218 B |
BIN
src/Android/Resources/drawable-hdpi/note.png
Normal file
|
After Width: | Height: | Size: 344 B |
BIN
src/Android/Resources/drawable-hdpi/notification_sm.png
Normal file
|
After Width: | Height: | Size: 531 B |
BIN
src/Android/Resources/drawable-hdpi/paperclip.png
Normal file
|
After Width: | Height: | Size: 658 B |
BIN
src/Android/Resources/drawable-hdpi/pencil.png
Normal file
|
After Width: | Height: | Size: 495 B |