Compare commits
1075 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5350e5385c | ||
|
|
8f18c4fd45 | ||
|
|
8538fbabe5 | ||
|
|
9367b34bbe | ||
|
|
a766044cb4 | ||
|
|
0eb385e49f | ||
|
|
e30136dace | ||
|
|
c50dee479a | ||
|
|
58ef292fa7 | ||
|
|
61b728fea7 | ||
|
|
b782eeb839 | ||
|
|
77314d4b8d | ||
|
|
c79d1d24b3 | ||
|
|
5dbea8ca09 | ||
|
|
09a1c17fb4 | ||
|
|
a0632bcac2 | ||
|
|
07d57ebe8c | ||
|
|
325c88018c | ||
|
|
dcb1102746 | ||
|
|
636d3c02c4 | ||
|
|
5b119ded17 | ||
|
|
f25ae870c5 | ||
|
|
49673262e4 | ||
|
|
3ed814c1f7 | ||
|
|
8b858e5407 | ||
|
|
30145894b6 | ||
|
|
8df4c27203 | ||
|
|
28166f79a1 | ||
|
|
2fe67137c5 | ||
|
|
a6ea2de691 | ||
|
|
7ffcde002e | ||
|
|
a3f66b8ab1 | ||
|
|
43cee53dc8 | ||
|
|
6c5979040f | ||
|
|
666291d12d | ||
|
|
c04a8ccdc2 | ||
|
|
b13d2f7f91 | ||
|
|
0b56dd2795 | ||
|
|
028ebff102 | ||
|
|
cd60c3da2a | ||
|
|
3d239a3c2b | ||
|
|
26d0077bb8 | ||
|
|
5dbe9e5ca2 | ||
|
|
5aac96edb5 | ||
|
|
c45b62fa6e | ||
|
|
e05708979d | ||
|
|
b6e7db6ecf | ||
|
|
17060a2a1b | ||
|
|
389fbc31e9 | ||
|
|
da3c3c30e7 | ||
|
|
42a7086088 | ||
|
|
5d4cc007bb | ||
|
|
a8dfb8e2b9 | ||
|
|
873999336f | ||
|
|
8476f53337 | ||
|
|
31a77a6e19 | ||
|
|
2376489f51 | ||
|
|
0bfd9f386e | ||
|
|
00e214871d | ||
|
|
fd41047923 | ||
|
|
393882f57b | ||
|
|
313e3a3447 | ||
|
|
23b3972503 | ||
|
|
01a64676b4 | ||
|
|
a8ee067215 | ||
|
|
504731885a | ||
|
|
76078348d6 | ||
|
|
464fb2144b | ||
|
|
c4e64ae80a | ||
|
|
a614d6634a | ||
|
|
856d7905db | ||
|
|
5b4be62fb4 | ||
|
|
44a58e3e9b | ||
|
|
28b6d2ee2e | ||
|
|
d749095d08 | ||
|
|
2cdf2cbe19 | ||
|
|
06669bc77b | ||
|
|
6cd6b00ea6 | ||
|
|
61bf43cd96 | ||
|
|
3be53235a5 | ||
|
|
0f0d9f9a10 | ||
|
|
e6cfa84b73 | ||
|
|
e5888e4b90 | ||
|
|
8e9e818c43 | ||
|
|
5f41487fd2 | ||
|
|
3b7d3e2652 | ||
|
|
5963cd35d5 | ||
|
|
c4ebd8b6b7 | ||
|
|
678640966e | ||
|
|
89f9394977 | ||
|
|
e7df752581 | ||
|
|
dca567b00d | ||
|
|
5248df85ec | ||
|
|
31d39e0626 | ||
|
|
5485bb574e | ||
|
|
337965a7f7 | ||
|
|
81cd2e26f4 | ||
|
|
95c07334d7 | ||
|
|
2f66ee264b | ||
|
|
5f16066641 | ||
|
|
6cdf84dcb7 | ||
|
|
d7bfc64840 | ||
|
|
d199c83a61 | ||
|
|
060e1a822f | ||
|
|
54141f77d6 | ||
|
|
d5c610b819 | ||
|
|
a2bedaab8a | ||
|
|
fa5c8c2c75 | ||
|
|
a2d00c4057 | ||
|
|
cf9595a0bc | ||
|
|
68d8b1fa3e | ||
|
|
6f905319c3 | ||
|
|
d6fd3edb3c | ||
|
|
5441c49d8d | ||
|
|
c7938a8630 | ||
|
|
bc0bb7c7bb | ||
|
|
1c58d99006 | ||
|
|
e0c52dea3a | ||
|
|
a1a5d3b363 | ||
|
|
9ae08932c2 | ||
|
|
7d8dfb997e | ||
|
|
c8d8d94c7f | ||
|
|
58f01493e0 | ||
|
|
4a01116e93 | ||
|
|
1cdc97f037 | ||
|
|
927c1a6799 | ||
|
|
8eec0c22d7 | ||
|
|
9d86fac103 | ||
|
|
65f2ea1df2 | ||
|
|
4d54c8f1d1 | ||
|
|
046f25c223 | ||
|
|
661cd79654 | ||
|
|
3b7078e565 | ||
|
|
e59ac5d65d | ||
|
|
67da9387ff | ||
|
|
c3f91761f6 | ||
|
|
a239ee80bc | ||
|
|
96275d8eec | ||
|
|
1ef086a02c | ||
|
|
db10fee207 | ||
|
|
279b53704b | ||
|
|
dbc750b44e | ||
|
|
b29ccf67b1 | ||
|
|
b67adf8789 | ||
|
|
113eaef0d1 | ||
|
|
6c88d25941 | ||
|
|
f6f6253eab | ||
|
|
8e1753ea37 | ||
|
|
83f8989d1f | ||
|
|
e1f5c97550 | ||
|
|
f8fa867154 | ||
|
|
f0893ca214 | ||
|
|
82a58d9487 | ||
|
|
d0ea6aedd0 | ||
|
|
775d929711 | ||
|
|
5bae62cdcb | ||
|
|
0a664c47b7 | ||
|
|
f856e559a2 | ||
|
|
0283120934 | ||
|
|
5237cbeab0 | ||
|
|
15961c419e | ||
|
|
8b591a7f0c | ||
|
|
f7977c3435 | ||
|
|
0c699e6c12 | ||
|
|
11e5b2ea5d | ||
|
|
fdfd2d20e6 | ||
|
|
32adb7b91a | ||
|
|
237d31be28 | ||
|
|
ee07182157 | ||
|
|
506ab12b95 | ||
|
|
42d6a7c9b8 | ||
|
|
44530b63ca | ||
|
|
eadba95d5f | ||
|
|
2574e0cba5 | ||
|
|
e03cf94441 | ||
|
|
b38b801963 | ||
|
|
4f990ffe96 | ||
|
|
8c54062c44 | ||
|
|
9766ebc8e3 | ||
|
|
07bc425046 | ||
|
|
d0b8c2c4bf | ||
|
|
83bc7c6d51 | ||
|
|
d64625aba2 | ||
|
|
d1eec27fae | ||
|
|
43517fc4f6 | ||
|
|
0e3eeab828 | ||
|
|
6b68e85077 | ||
|
|
c9faa40475 | ||
|
|
f8969675e7 | ||
|
|
d620b70dac | ||
|
|
4055f92831 | ||
|
|
3e50461ea7 | ||
|
|
5eee358059 | ||
|
|
17b90c744c | ||
|
|
a78997771f | ||
|
|
3a691977aa | ||
|
|
4bea105693 | ||
|
|
0918e1d963 | ||
|
|
11ec9b320e | ||
|
|
97e98e1d9d | ||
|
|
e6dcdc51a4 | ||
|
|
702d27fea0 | ||
|
|
59b086168a | ||
|
|
57c89f5b2a | ||
|
|
b69304992f | ||
|
|
a5256a6491 | ||
|
|
8211f85725 | ||
|
|
c27e44a7d2 | ||
|
|
22f16ea514 | ||
|
|
7ed45c3535 | ||
|
|
a3716bc841 | ||
|
|
e8c776fe49 | ||
|
|
999c9667c8 | ||
|
|
59620e6435 | ||
|
|
cf4a6bb1a2 | ||
|
|
8658ebd6bb | ||
|
|
59f1dcca12 | ||
|
|
4382490823 | ||
|
|
479ad31325 | ||
|
|
dd976b5b86 | ||
|
|
18c2c1433b | ||
|
|
0fc013eb8a | ||
|
|
1d95c73173 | ||
|
|
312b6c2d44 | ||
|
|
cab2b261b5 | ||
|
|
2ddb384c80 | ||
|
|
ca27b34122 | ||
|
|
5ce2eaf77e | ||
|
|
4ae02c27a4 | ||
|
|
50f71e7280 | ||
|
|
638de90cc4 | ||
|
|
d1646e5aca | ||
|
|
ddd521cd4b | ||
|
|
f5b05ecd92 | ||
|
|
4f2b76c442 | ||
|
|
6aef106482 | ||
|
|
b191542ab7 | ||
|
|
e0e69920e9 | ||
|
|
f3edae9bb1 | ||
|
|
0d5f5ec9ba | ||
|
|
582799464f | ||
|
|
fc0d91d3e7 | ||
|
|
81b8c1716f | ||
|
|
b5d2a9a2fb | ||
|
|
a98283f3ff | ||
|
|
61be796c76 | ||
|
|
dd090b0ed9 | ||
|
|
6588b5bebb | ||
|
|
aa3fd29508 | ||
|
|
dcf412d94d | ||
|
|
defac4e2d5 | ||
|
|
6163a6dd77 | ||
|
|
32e757a873 | ||
|
|
8df940447d | ||
|
|
ac6f3a6bb6 | ||
|
|
46e631388d | ||
|
|
ff7c9f210c | ||
|
|
eecf81f0a5 | ||
|
|
b356627afd | ||
|
|
e3dcf4aed1 | ||
|
|
4dd05cf10e | ||
|
|
68a85a2561 | ||
|
|
49cde1c67d | ||
|
|
249dffe778 | ||
|
|
6d9b860c8b | ||
|
|
b1eabdfe86 | ||
|
|
ab9bbf7b0f | ||
|
|
cf2308a12d | ||
|
|
0aa1359ad4 | ||
|
|
48c51173a1 | ||
|
|
6838b32304 | ||
|
|
60f81c5cba | ||
|
|
419cfceef1 | ||
|
|
c7b62c8551 | ||
|
|
a66489cd8c | ||
|
|
6d51864873 | ||
|
|
dc7b37c8f2 | ||
|
|
21bbb2af42 | ||
|
|
c3b9f4e5a8 | ||
|
|
70fa41ca3e | ||
|
|
49705b1074 | ||
|
|
32395dcb20 | ||
|
|
e34ca49b9b | ||
|
|
46a8ffa5ae | ||
|
|
3f8f29dfe7 | ||
|
|
67970afc1e | ||
|
|
1c08901698 | ||
|
|
86d8d64bf6 | ||
|
|
cc8b8f9ceb | ||
|
|
6e91d66b2c | ||
|
|
886f356525 | ||
|
|
3fe7324cdf | ||
|
|
a1741fdd67 | ||
|
|
40484a7bf0 | ||
|
|
48376d0a93 | ||
|
|
6be54fa7ee | ||
|
|
6c6da368dd | ||
|
|
d70de04816 | ||
|
|
bf022f8a41 | ||
|
|
7046a010f9 | ||
|
|
5cdccc7527 | ||
|
|
02552d0b24 | ||
|
|
026dd4761e | ||
|
|
635dd5d4d5 | ||
|
|
81d972a462 | ||
|
|
bfeddd6d99 | ||
|
|
437a60a967 | ||
|
|
6218c65ec6 | ||
|
|
25d02cec25 | ||
|
|
0bbb549533 | ||
|
|
4ca5593d2e | ||
|
|
630fc3f73c | ||
|
|
d66646d168 | ||
|
|
ffe7771801 | ||
|
|
5942bcc16d | ||
|
|
eb3d0d726f | ||
|
|
a4846c7b11 | ||
|
|
8e7ed05f25 | ||
|
|
84d21efb5d | ||
|
|
68ee013114 | ||
|
|
cd8a09ddef | ||
|
|
e1f6302280 | ||
|
|
144197daaf | ||
|
|
57af4e0a03 | ||
|
|
309caa2f83 | ||
|
|
3f11fdaa82 | ||
|
|
faccb61a6b | ||
|
|
786f3b4644 | ||
|
|
58d101659a | ||
|
|
822fc7f308 | ||
|
|
29ab7f7a30 | ||
|
|
bf4f0bdba0 | ||
|
|
e8705d49f2 | ||
|
|
6d49253ee5 | ||
|
|
23f27282d6 | ||
|
|
bfa336b72d | ||
|
|
b5522c1b5e | ||
|
|
543e3418a5 | ||
|
|
ea3dcd6250 | ||
|
|
b8cbd5e0aa | ||
|
|
29951207ec | ||
|
|
3c58775ae2 | ||
|
|
9ab41c5de6 | ||
|
|
65f3a146fa | ||
|
|
22366ec0a2 | ||
|
|
d038fb900e | ||
|
|
13c5ebe065 | ||
|
|
4112e0a4c9 | ||
|
|
f73a5d6307 | ||
|
|
a3e165fa06 | ||
|
|
3e633dc38e | ||
|
|
d53bfae529 | ||
|
|
9d491a3636 | ||
|
|
c77d4b795a | ||
|
|
c96daf2d68 | ||
|
|
961e23f0b8 | ||
|
|
defb67c523 | ||
|
|
4aa1209bc7 | ||
|
|
a7ad89471a | ||
|
|
1f74e102fa | ||
|
|
5761b47073 | ||
|
|
22a0b262e1 | ||
|
|
6e42f5ce7b | ||
|
|
c5bd59e52c | ||
|
|
c65b065dd7 | ||
|
|
3f8ab1bfe7 | ||
|
|
cf41c5e090 | ||
|
|
290e90ba8e | ||
|
|
84c9516659 | ||
|
|
3eb1ab0452 | ||
|
|
f317f45d14 | ||
|
|
5f6a3f4cb5 | ||
|
|
27b6631cc1 | ||
|
|
f7bb091366 | ||
|
|
5cf2092576 | ||
|
|
954de743f5 | ||
|
|
8ab632e207 | ||
|
|
547cd4e828 | ||
|
|
264028b623 | ||
|
|
2c302985f8 | ||
|
|
d6f46cf5c5 | ||
|
|
d4ed276684 | ||
|
|
67e458833f | ||
|
|
291c201b00 | ||
|
|
f9473ea61d | ||
|
|
b68031bd11 | ||
|
|
879b5ef3aa | ||
|
|
ff1f895476 | ||
|
|
3229835f50 | ||
|
|
f0a96759a4 | ||
|
|
4f4c6064db | ||
|
|
df802152d7 | ||
|
|
4f3a3a5b19 | ||
|
|
cc7ccf921b | ||
|
|
4dfd8b6985 | ||
|
|
919eedc0fa | ||
|
|
9400c22e4f | ||
|
|
99f00b8e63 | ||
|
|
29d876eaed | ||
|
|
018a7c9f96 | ||
|
|
28473dd85f | ||
|
|
29b37219c2 | ||
|
|
34fd9b5842 | ||
|
|
2b2787b187 | ||
|
|
253217cf20 | ||
|
|
74c0e52458 | ||
|
|
75cb67890d | ||
|
|
9668bd85c1 | ||
|
|
9aef584494 | ||
|
|
a535e8a82b | ||
|
|
3bd01067e3 | ||
|
|
056b9fd2de | ||
|
|
da2ec4a38e | ||
|
|
fb5c36071d | ||
|
|
b07afa7f11 | ||
|
|
a0e65fa75e | ||
|
|
866a20ed5a | ||
|
|
49c355e52f | ||
|
|
8d0dc2d230 | ||
|
|
cbda58f770 | ||
|
|
bb0ee239b4 | ||
|
|
0495c17fc8 | ||
|
|
2a5739dfdc | ||
|
|
0b73317dd0 | ||
|
|
5dc4357078 | ||
|
|
cf2ae9d7e8 | ||
|
|
68c6537bcb | ||
|
|
a236d7fbb1 | ||
|
|
13267ff83d | ||
|
|
8c31c7290c | ||
|
|
7d3ef39f67 | ||
|
|
51ee93eca0 | ||
|
|
2816e72aa9 | ||
|
|
5ef4c96ee7 | ||
|
|
4ed12a859b | ||
|
|
128935eb9f | ||
|
|
2553938380 | ||
|
|
59e412ea09 | ||
|
|
9195bdcf95 | ||
|
|
2ca066327a | ||
|
|
daa9bdfb8c | ||
|
|
faf690d899 | ||
|
|
9965121011 | ||
|
|
15cda95c64 | ||
|
|
9678eab43f | ||
|
|
3b97fa0379 | ||
|
|
c8368a2190 | ||
|
|
ba9605383d | ||
|
|
caaec3ea57 | ||
|
|
a018369ae8 | ||
|
|
b4f4f24c24 | ||
|
|
9eeafcd027 | ||
|
|
464f4ba300 | ||
|
|
cad56d438f | ||
|
|
5812dc7e7e | ||
|
|
4a1bccd516 | ||
|
|
e46277c2ac | ||
|
|
33684bd140 | ||
|
|
865deaf401 | ||
|
|
1f4bdb04ee | ||
|
|
a5bf16a415 | ||
|
|
2a6fe3f351 | ||
|
|
9468bb322d | ||
|
|
9650e44078 | ||
|
|
134a4ec5d2 | ||
|
|
b7d87486a8 | ||
|
|
a75295662c | ||
|
|
69eeb8bd23 | ||
|
|
65fe0e8f62 | ||
|
|
8fa2ef863f | ||
|
|
5e114e8074 | ||
|
|
223e8a5293 | ||
|
|
aee9ce802a | ||
|
|
04bd6b49ad | ||
|
|
e9d54d9cf5 | ||
|
|
9b2ce98b46 | ||
|
|
d7312e2977 | ||
|
|
c2a168c6b7 | ||
|
|
a01f9c7351 | ||
|
|
dc0f9847c6 | ||
|
|
7613f4191b | ||
|
|
1b613db534 | ||
|
|
fb50123af0 | ||
|
|
08c3aff60b | ||
|
|
9cb141ef62 | ||
|
|
baf77eb3a3 | ||
|
|
0625f313dc | ||
|
|
b644ed5a25 | ||
|
|
18ff6c7976 | ||
|
|
be5fd8381f | ||
|
|
003092a55b | ||
|
|
3d50133fa8 | ||
|
|
594a491251 | ||
|
|
1cde9b8356 | ||
|
|
b107df60e2 | ||
|
|
997ed42178 | ||
|
|
913cd23c45 | ||
|
|
6cd1171fd5 | ||
|
|
c3fe3292ad | ||
|
|
b48c231500 | ||
|
|
a6cd16cdb5 | ||
|
|
2959577163 | ||
|
|
efddb385d8 | ||
|
|
ea5a411f30 | ||
|
|
7548122e2d | ||
|
|
a4cbf3bee3 | ||
|
|
7f2c265965 | ||
|
|
c6ce3e9480 | ||
|
|
6d8af4d97a | ||
|
|
60efcbaf02 | ||
|
|
3b63fbb61b | ||
|
|
2637587cf7 | ||
|
|
480f954433 | ||
|
|
7c1549bb95 | ||
|
|
4b7366e9b3 | ||
|
|
6288a06b49 | ||
|
|
b3c67bab92 | ||
|
|
8c8fa8ae4c | ||
|
|
0c93fc2662 | ||
|
|
aec2855db9 | ||
|
|
2becf769c1 | ||
|
|
da73a2f5d2 | ||
|
|
1dd5f9fc27 | ||
|
|
e141991166 | ||
|
|
fff32e8086 | ||
|
|
859788ca46 | ||
|
|
b94485be75 | ||
|
|
43948c65f1 | ||
|
|
b61355a8fa | ||
|
|
8834492ec2 | ||
|
|
676e896d8c | ||
|
|
6d159740d9 | ||
|
|
f48aa24129 | ||
|
|
f46151bb71 | ||
|
|
818414eb37 | ||
|
|
808fcea655 | ||
|
|
d050e01d08 | ||
|
|
6cab060509 | ||
|
|
18f04af051 | ||
|
|
4b3bae5797 | ||
|
|
98291caf76 | ||
|
|
a1a8c95ece | ||
|
|
40b6460ac9 | ||
|
|
25c82ffd58 | ||
|
|
f76051d362 | ||
|
|
351c99fb42 | ||
|
|
803527f585 | ||
|
|
d1c696bad5 | ||
|
|
5d2f4e6ca4 | ||
|
|
567ebcd06e | ||
|
|
1103354de3 | ||
|
|
2fa3d214e5 | ||
|
|
7d6ec46ebe | ||
|
|
40598721f1 | ||
|
|
576f44a924 | ||
|
|
694e4960ad | ||
|
|
4b67ba027e | ||
|
|
eeb28f6ddf | ||
|
|
4aa5ba2754 | ||
|
|
f228758fb7 | ||
|
|
d136eee224 | ||
|
|
52a978a59a | ||
|
|
8ade49c958 | ||
|
|
7c6c36b744 | ||
|
|
6c59bf8717 | ||
|
|
87798612a6 | ||
|
|
c89805d123 | ||
|
|
a1ba2bf60b | ||
|
|
87543f5beb | ||
|
|
2e31a7b280 | ||
|
|
9fb2ce9297 | ||
|
|
567d527a71 | ||
|
|
6ee109dc80 | ||
|
|
6a65b6d735 | ||
|
|
b9838ecc4e | ||
|
|
e1080f9be4 | ||
|
|
115fa349d2 | ||
|
|
567161d8f3 | ||
|
|
579a7e0398 | ||
|
|
0d417b3eee | ||
|
|
9e51c46522 | ||
|
|
36780c5ef8 | ||
|
|
9e3ee50020 | ||
|
|
963b27fd71 | ||
|
|
13a2206735 | ||
|
|
929727ba41 | ||
|
|
a0f6af8097 | ||
|
|
f79f93ba43 | ||
|
|
0898e3cc09 | ||
|
|
c68927c17a | ||
|
|
9f740cf371 | ||
|
|
474ce458bf | ||
|
|
8c6823c463 | ||
|
|
8055de4f25 | ||
|
|
d1c1aff7d9 | ||
|
|
41321e3c9e | ||
|
|
992cf033f2 | ||
|
|
24b4073616 | ||
|
|
4ef8ccaa8e | ||
|
|
f0eca137ef | ||
|
|
3f5115728b | ||
|
|
3539d7389e | ||
|
|
8c79c42b28 | ||
|
|
1085808867 | ||
|
|
0a5f9f1b23 | ||
|
|
8006bcf10c | ||
|
|
61e95e03c8 | ||
|
|
2b2342bcad | ||
|
|
56b8bc1730 | ||
|
|
53aaf7caa8 | ||
|
|
18f659de7b | ||
|
|
b2c0b02be4 | ||
|
|
61c1c6ebf8 | ||
|
|
b837c600a8 | ||
|
|
54a109345b | ||
|
|
3a90b3beb6 | ||
|
|
fe52324eea | ||
|
|
a1c853d7fc | ||
|
|
53974c4464 | ||
|
|
69ac98b2f6 | ||
|
|
d84eece715 | ||
|
|
af7a456e96 | ||
|
|
6088cfe266 | ||
|
|
02e03c7654 | ||
|
|
30dd2e993f | ||
|
|
574c826036 | ||
|
|
6d22888bf6 | ||
|
|
556741683d | ||
|
|
cb9dddc7a7 | ||
|
|
fca1dbd6ec | ||
|
|
364f25e22a | ||
|
|
9042702c09 | ||
|
|
86f1874379 | ||
|
|
781f838ce7 | ||
|
|
addb7a0ecb | ||
|
|
21777602f6 | ||
|
|
a88f799372 | ||
|
|
daee1a8e9d | ||
|
|
afd5d55b5f | ||
|
|
0f2d2ac7a9 | ||
|
|
a77bf9ddff | ||
|
|
297beac169 | ||
|
|
5a7f106e3e | ||
|
|
25636ac6ce | ||
|
|
6d11921f45 | ||
|
|
05f6d6d156 | ||
|
|
b0cabbbfc2 | ||
|
|
4b9fa5041c | ||
|
|
ed2ea75938 | ||
|
|
a1fdc4a970 | ||
|
|
d20b3cadbf | ||
|
|
1374ee31ba | ||
|
|
062f6b6665 | ||
|
|
8053e6cb81 | ||
|
|
60c5bbc73d | ||
|
|
f0fb401d07 | ||
|
|
6f3c302f28 | ||
|
|
ba5495877c | ||
|
|
2b8fe5fa98 | ||
|
|
c4253e63d2 | ||
|
|
8c1dc9c4ae | ||
|
|
bfddb2b4d6 | ||
|
|
b53ba52d05 | ||
|
|
0694721e68 | ||
|
|
bc0084cc8b | ||
|
|
d3d4cca0e8 | ||
|
|
5b6becc63f | ||
|
|
7ed015aeb1 | ||
|
|
a8b84da17f | ||
|
|
2d687c98df | ||
|
|
9c2a6da692 | ||
|
|
ee9ecfbee9 | ||
|
|
9d8a9387bc | ||
|
|
8934cebc2a | ||
|
|
c03a42e108 | ||
|
|
a39dfcf4d5 | ||
|
|
f25ab537f2 | ||
|
|
ff776e485a | ||
|
|
3b9f4433ad | ||
|
|
a8c18cf83a | ||
|
|
dff91dc67d | ||
|
|
fdea212415 | ||
|
|
421f7e8799 | ||
|
|
8fc5ad099b | ||
|
|
0b1c0be0f0 | ||
|
|
5cc1e2bb29 | ||
|
|
c0eb84b7b1 | ||
|
|
66e8d955d9 | ||
|
|
e280f585cf | ||
|
|
366578600b | ||
|
|
8611501423 | ||
|
|
8175af4e84 | ||
|
|
7ff628ea51 | ||
|
|
5792372a47 | ||
|
|
9a169251d7 | ||
|
|
2f0935dbbe | ||
|
|
5af7c7d58a | ||
|
|
9e1abb13a3 | ||
|
|
bbea5fe53c | ||
|
|
ed087b81b4 | ||
|
|
0b06c87cb7 | ||
|
|
a9d204d3fa | ||
|
|
36e263b9ff | ||
|
|
49af74729f | ||
|
|
aec0415cad | ||
|
|
8c8cec08e5 | ||
|
|
8d0ee1caba | ||
|
|
a2a6d5a57f | ||
|
|
9593f330db | ||
|
|
144d932591 | ||
|
|
726db336a8 | ||
|
|
8632f7cadc | ||
|
|
525b5fa19a | ||
|
|
b7c6b65a3d | ||
|
|
a4f93eea45 | ||
|
|
8d9a5bc08f | ||
|
|
28a93cdcb4 | ||
|
|
d32dd95e12 | ||
|
|
f19a8a3cc5 | ||
|
|
f8d5ee9d5d | ||
|
|
a98fb7d41e | ||
|
|
d2ccc5c022 | ||
|
|
3339c8b676 | ||
|
|
2cc331b136 | ||
|
|
df1a65e6a3 | ||
|
|
f416f95b77 | ||
|
|
44fe5af4fb | ||
|
|
f394eddc01 | ||
|
|
07dd9df3ec | ||
|
|
340d4ce714 | ||
|
|
2870b8331e | ||
|
|
bc79f22e22 | ||
|
|
b3e0b148a6 | ||
|
|
6131dfeb2c | ||
|
|
42fc868b68 | ||
|
|
e9ec02929d | ||
|
|
389d7d77c2 | ||
|
|
03a55d38d3 | ||
|
|
5504298251 | ||
|
|
0fff61de34 | ||
|
|
4db7311611 | ||
|
|
650d970b5e | ||
|
|
7f796963f3 | ||
|
|
9231da2f1c | ||
|
|
0d422c53d1 | ||
|
|
7bf9354e32 | ||
|
|
277fd67814 | ||
|
|
68e9cd3779 | ||
|
|
73b2ae71a9 | ||
|
|
f9028245d8 | ||
|
|
3fff0617fe | ||
|
|
d958dc6bce | ||
|
|
045ce42168 | ||
|
|
edef84a4f9 | ||
|
|
ec9b2d7f7d | ||
|
|
640beeed23 | ||
|
|
a998ee84ec | ||
|
|
bd74634201 | ||
|
|
364287028b | ||
|
|
7be8513bb5 | ||
|
|
89c2f62f11 | ||
|
|
fce2a7ba94 | ||
|
|
e613198252 | ||
|
|
3e240c4d2f | ||
|
|
eee96bf8cf | ||
|
|
471871eb2e | ||
|
|
225b8b8cd8 | ||
|
|
cb2d133eb0 | ||
|
|
d957111249 | ||
|
|
f0a701e134 | ||
|
|
0c8c216fba | ||
|
|
f153c7509c | ||
|
|
197683b722 | ||
|
|
fbce0be457 | ||
|
|
24304c2f55 | ||
|
|
b0c6c09cea | ||
|
|
2732fc93f9 | ||
|
|
cc9a4a288a | ||
|
|
f57db917d1 | ||
|
|
d32eb9c9bc | ||
|
|
219af7f288 | ||
|
|
e767b6c5be | ||
|
|
5f1a8017f1 | ||
|
|
df80122ce1 | ||
|
|
6904ea118b | ||
|
|
0baa07daa2 | ||
|
|
2abf407b36 | ||
|
|
55dc9cbfc7 | ||
|
|
fa15a10ae0 | ||
|
|
e821d8a135 | ||
|
|
cefcc14d4d | ||
|
|
a52d1e1506 | ||
|
|
42df1aecd5 | ||
|
|
920f4fc525 | ||
|
|
d57d583076 | ||
|
|
1c8cd2dcbb | ||
|
|
bb66a6c805 | ||
|
|
10ecdfbe31 | ||
|
|
43dad1df63 | ||
|
|
fdcc6b0cb8 | ||
|
|
ac82d6bc6b | ||
|
|
a8e98cfa78 | ||
|
|
fa51314639 | ||
|
|
3e0097c22c | ||
|
|
e62ff2fe36 | ||
|
|
88a13eb54f | ||
|
|
355dc151c4 | ||
|
|
4f0ea44078 | ||
|
|
9cc12fd577 | ||
|
|
960c2567bd | ||
|
|
430e4dd445 | ||
|
|
1ec31c6899 | ||
|
|
42c21ce892 | ||
|
|
d278fde5f2 | ||
|
|
2616a5f500 | ||
|
|
cc58f7730e | ||
|
|
d49e001b21 | ||
|
|
f12ceb69ce | ||
|
|
7862005055 | ||
|
|
e70dbf8d8d | ||
|
|
16ee34d7a7 | ||
|
|
2ad55e8a8c | ||
|
|
a8a1750d5c | ||
|
|
5407e20150 | ||
|
|
58aa37bf8e | ||
|
|
7c781b60c5 | ||
|
|
acdfce7e88 | ||
|
|
4e4b56d7fe | ||
|
|
a3174d7015 | ||
|
|
b595c8aeac | ||
|
|
a9a33ad71e | ||
|
|
4d08ce90cc | ||
|
|
01d9ccc110 | ||
|
|
029f069ad5 | ||
|
|
13b9e01604 | ||
|
|
19c46a472a | ||
|
|
37edfffb97 | ||
|
|
67fa653d06 | ||
|
|
366b9ddc4a | ||
|
|
c9354f79b8 | ||
|
|
d5b3bd5905 | ||
|
|
45dd240415 | ||
|
|
bf99cea004 | ||
|
|
f680b1e856 | ||
|
|
b2f40c7af0 | ||
|
|
82b17677b9 | ||
|
|
b7df2d5441 | ||
|
|
dd511ba365 | ||
|
|
d705e3c1fb | ||
|
|
4bd1322904 | ||
|
|
ac027ee3a0 | ||
|
|
e5e3ebdbba | ||
|
|
993fc2e6f4 | ||
|
|
61c480618c | ||
|
|
db7f2622c8 | ||
|
|
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 |
3
.editorconfig
Normal file
@@ -0,0 +1,3 @@
|
||||
# All files
|
||||
[*]
|
||||
guidelines = 120
|
||||
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
||||
3
.gitignore
vendored
@@ -197,4 +197,5 @@ FakesAssemblies/
|
||||
|
||||
# Other
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
src/App/Css
|
||||
@@ -6,8 +6,8 @@ Code contributions are welcome! Please commit any pull requests against the `mas
|
||||
|
||||
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 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).
|
||||
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/profile/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
|
||||
-->
|
||||
21
README.md
@@ -2,29 +2,30 @@
|
||||
[](https://crowdin.com/project/bitwarden-mobile)
|
||||
[](https://gitter.im/bitwarden/Lobby)
|
||||
|
||||
# bitwarden mobile
|
||||
# Bitwarden Mobile Application
|
||||
|
||||
<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>
|
||||
<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://mobileapp.bitwarden.com/fdroid/" target="_blank"><img alt="Get it on Google Play" src="https://i.imgur.com/HDicnzz.png" width="154" 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, and Xamarin Forms.
|
||||
|
||||
<img src="https://i.imgur.com/Ty8wkSO.png" alt="" width="300" height="533" /> <img src="https://i.imgur.com/BYb4gVc.png" alt="" width="300" height="533" />
|
||||
<img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-android-myvault.png" alt="" width="300" height="533" /> <img src="https://raw.githubusercontent.com/bitwarden/brand/master/screenshots/mobile-ios.png" alt="" width="300" height="533" />
|
||||
|
||||
# Build/Run
|
||||
|
||||
**Requirements**
|
||||
|
||||
- [Visual Studio w/ Xamarin -or- Xamarin Studio](https://store.xamarin.com/)
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/)
|
||||
- [Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/?pivots=windows)
|
||||
|
||||
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.
|
||||
- Open the solution file in Visual Studio.
|
||||
- Restore the nuget packages.
|
||||
- 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.
|
||||
Code contributions are welcome! Visual Studio with Xamarin 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. You can read our security policy in the [`SECURITY.md`](SECURITY.md) file.
|
||||
|
||||
16
SECURITY.md
@@ -1,4 +1,4 @@
|
||||
bitwarden believes that working with security researchers across the globe is crucial to keeping our
|
||||
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!
|
||||
|
||||
@@ -16,7 +16,7 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
||||
|
||||
# In-scope
|
||||
|
||||
- Security issues in any current release of bitwarden. This includes the web vault, browser extension,
|
||||
- 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.
|
||||
|
||||
@@ -24,14 +24,14 @@ notify us. We welcome working with you to resolve the issue promptly. Thanks in
|
||||
|
||||
The following bug classes are out-of scope:
|
||||
|
||||
- Bugs that are already reported on any of bitwarden's issue trackers (https://github.com/bitwarden),
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -39,7 +39,7 @@ 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
|
||||
- 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!
|
||||
Thank you for helping keep Bitwarden and our users safe!
|
||||
|
||||
158
appveyor.yml
@@ -1,21 +1,143 @@
|
||||
install:
|
||||
- appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||
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\increment-version.ps1 $($env:APPVEYOR_BUILD_FOLDER) $($env:APPVEYOR_BUILD_NUMBER) }
|
||||
- IF DEFINED keystore_dec_secret secure-file\tools\secure-file -decrypt src\Android\8bit.keystore.enc -secret %keystore_dec_secret%
|
||||
- IF DEFINED keystore_password msbuild "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" "/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=%keystore_password%" "/p:AndroidSigningKeyStore=8bit.keystore" "/p:AndroidSigningStorePass=%keystore_password%" "src\Android\Android.csproj"
|
||||
- ps: IF($env:keystore_dec_secret) { copy-item src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk .\com.x8bit.bitwarden-$($env:APPVEYOR_BUILD_NUMBER).apk }
|
||||
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\Debug\netcoreapp2.0\Publisher.dll %APPVEYOR_BUILD_FOLDER%\store\google\Publisher\play_creds.json %APPVEYOR_BUILD_FOLDER%\src\Android\bin\Release\com.x8bit.bitwarden-Signed.apk alpha
|
||||
artifacts:
|
||||
- path: com.x8bit.bitwarden-%APPVEYOR_BUILD_NUMBER%.apk
|
||||
image:
|
||||
- Visual Studio 2017
|
||||
- Ubuntu1804
|
||||
|
||||
branches:
|
||||
except:
|
||||
- l10n_master
|
||||
skip_tags: true
|
||||
image: Visual Studio 2017
|
||||
- gh-pages
|
||||
|
||||
configuration: Release
|
||||
|
||||
stack: node 10
|
||||
|
||||
init:
|
||||
- sh: |
|
||||
if [ "${DEBUG_SSH}" == "true" ]
|
||||
then
|
||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
|
||||
fi
|
||||
- ps: |
|
||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
||||
iex ((new-object net.webclient).DownloadString(`
|
||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
}
|
||||
- ps: |
|
||||
if($env:APPVEYOR_REPO_TAG -eq "true") {
|
||||
$tagName = $env:APPVEYOR_REPO_TAG_NAME.TrimStart("v")
|
||||
$env:RELEASE_NAME = "Version ${tagName}"
|
||||
}
|
||||
|
||||
install:
|
||||
- sh: |
|
||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/secure-file/master/install.sh' | bash -e -
|
||||
./appveyor-tools/secure-file -decrypt ./store/fdroid/keystore.jks.enc -secret $FDROID_KEYSTORE_ENC_PASSWORD
|
||||
- sh: npm install
|
||||
- sh: |
|
||||
sudo apt-get -qq update
|
||||
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
|
||||
- sh: |
|
||||
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
|
||||
then
|
||||
git config --global credential.helper store
|
||||
echo "https://${GH_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
|
||||
git config --global user.email "ci@bitwarden.com"
|
||||
git config --global user.name "Bitwarden CI"
|
||||
fi
|
||||
- cmd: choco install cloc --no-progress
|
||||
- cmd: "cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML"
|
||||
#- cmd: appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
|
||||
#- cmd: appveyor DownloadFile https://aka.ms/vs/15/release/vs_community.exe
|
||||
#- cmd: vs_community.exe update --wait --quiet --norestart --installPath "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community"
|
||||
#- cmd: ps: .\src\Android\update-android.ps1
|
||||
|
||||
before_build:
|
||||
- ps: |
|
||||
if($isWindows) {
|
||||
nuget restore
|
||||
if($env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
|
||||
nuget install secure-file -ExcludeVersion
|
||||
}
|
||||
if($env:GOOGLE_SERVICES_DEC_SECRET) {
|
||||
secure-file\tools\secure-file -decrypt src\Android\google-services.json.enc `
|
||||
-secret $env:GOOGLE_SERVICES_DEC_SECRET
|
||||
}
|
||||
}
|
||||
|
||||
build_script:
|
||||
- sh: |
|
||||
if [ "${APPVEYOR_REPO_TAG}" == "true" ]
|
||||
then
|
||||
mkdir dist
|
||||
cp CNAME ./dist
|
||||
cd store
|
||||
chmod 600 fdroid/config.py fdroid/keystore.jks
|
||||
mkdir -p temp/fdroid
|
||||
TEMP_DIR="$APPVEYOR_BUILD_FOLDER/store/temp/fdroid"
|
||||
cd fdroid
|
||||
echo "keypass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
|
||||
echo "keystorepass=\"$FDROID_KEYSTORE_PASSWORD\"" >>config.py
|
||||
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
|
||||
mkdir -p repo
|
||||
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
|
||||
https://github.com/bitwarden/mobile/releases/download/$APPVEYOR_REPO_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
|
||||
fdroid update
|
||||
fdroid server update
|
||||
cd ..
|
||||
rm -rf temp/fdroid/archive
|
||||
mv -v temp/fdroid ../dist
|
||||
cd fdroid
|
||||
cp index.html btn.png qr.png ../../dist/fdroid
|
||||
cd $APPVEYOR_BUILD_FOLDER
|
||||
fi
|
||||
- ps: |
|
||||
if($isWindows -and $env:KEYSTORE_DEC_SECRET) {
|
||||
msbuild bitwarden-mobile.sln `
|
||||
"/logger:C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" `
|
||||
"/p:Configuration=Release"
|
||||
.\src\Android\ci-build-apks.ps1
|
||||
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
|
||||
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
|
||||
}
|
||||
|
||||
on_success:
|
||||
- sh: |
|
||||
if [ "${APPVEYOR_REPO_TAG}" == "true" -a "${GH_TOKEN}" != "" ]
|
||||
then
|
||||
npm run deploy
|
||||
fi
|
||||
- ps: |
|
||||
if($isWindows -and $env:PLAY_DEC_SECRET) {
|
||||
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
|
||||
cd store\google\Publisher\bin\Release\netcoreapp2.0
|
||||
dotnet Publisher.dll `
|
||||
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
|
||||
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.apk `
|
||||
alpha
|
||||
cd $env:APPVEYOR_BUILD_FOLDER
|
||||
}
|
||||
|
||||
on_finish:
|
||||
- sh: |
|
||||
if [ "${DEBUG_SSH}" == "true" ]
|
||||
then
|
||||
export APPVEYOR_SSH_BLOCK=true
|
||||
curl -sflL 'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-ssh.sh' | bash -e -
|
||||
fi
|
||||
- ps: |
|
||||
if($isWindows -and $env:DEBUG_RDP -eq "true") {
|
||||
$blockRdp = $true
|
||||
iex ((new-object net.webclient).DownloadString(`
|
||||
'https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
}
|
||||
|
||||
deploy:
|
||||
tag: $(APPVEYOR_REPO_TAG_NAME)
|
||||
release: $(RELEASE_NAME)
|
||||
provider: GitHub
|
||||
auth_token: $(GH_TOKEN)
|
||||
artifact: /.*/
|
||||
force_update: true
|
||||
on:
|
||||
branch: master
|
||||
APPVEYOR_REPO_TAG: true
|
||||
|
||||
@@ -1,450 +1,306 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2003
|
||||
VisualStudioVersion = 15.0.28307.539
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{04B18ED2-B76D-4947-8474-191F8FD2B5E0}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android", "src\Android\Android.csproj", "{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{1F78403F-9A28-405B-9289-B9DBEB55F074}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Core", "src\Core\Core.csproj", "{4B8A8C41-9820-4341-974C-41E65B7F4366}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EC730FD9-F623-4B6C-B503-95CDCFBCF277}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Playground", "test\Playground\Playground.csproj", "{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "App.Test", "test\App.Test\App.Test.csproj", "{A300DCE1-8D10-4267-B96A-CB01AEB7C220}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D10CA4A9-F866-40E1-B658-F69051236C71}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Extension", "src\iOS.Extension\iOS.Extension.csproj", "{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{B2538ADA-B605-4D6F-ACD2-62A409680F84}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{8904C536-C67D-420F-9971-51B26574C3AA}"
|
||||
EndProject
|
||||
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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UWP", "src\UWP\UWP.csproj", "{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}"
|
||||
EndProject
|
||||
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "UWP.Images", "src\UWP.Images\UWP.Images.shproj", "{0BE54BBB-7772-4289-BD51-1FDBB0CC2446}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "App", "src\App\App.csproj", "{8A279EE4-4537-4656-9C93-44945E594556}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}"
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{76690DFB-B7F4-4781-83E4-113FDC450AFE}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
.gitignore = .gitignore
|
||||
appveyor.yml = appveyor.yml
|
||||
CONTRIBUTING.md = CONTRIBUTING.md
|
||||
crowdin.yml = crowdin.yml
|
||||
README.md = README.md
|
||||
SECURITY.md = SECURITY.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Core", "src\iOS.Core\iOS.Core.csproj", "{E71F3053-056C-4381-9638-048ED73BDFF6}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "src\iOS\iOS.csproj", "{599E0201-420A-4C3E-A7BA-5349F72E0B15}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SharedMSBuildProjectFiles) = preSolution
|
||||
src\UWP.Images\UWP.Images.projitems*{0be54bbb-7772-4289-bd51-1fdbb0cc2446}*SharedItemsImports = 13
|
||||
src\UWP.Images\UWP.Images.projitems*{3a2d5669-ed71-4f2b-ba85-2d36baa05141}*SharedItemsImports = 4
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
|
||||
Ad-Hoc|ARM = Ad-Hoc|ARM
|
||||
Ad-Hoc|iPhone = Ad-Hoc|iPhone
|
||||
Ad-Hoc|iPhoneSimulator = Ad-Hoc|iPhoneSimulator
|
||||
Ad-Hoc|x64 = Ad-Hoc|x64
|
||||
Ad-Hoc|x86 = Ad-Hoc|x86
|
||||
AppStore|Any CPU = AppStore|Any CPU
|
||||
AppStore|ARM = AppStore|ARM
|
||||
AppStore|iPhone = AppStore|iPhone
|
||||
AppStore|iPhoneSimulator = AppStore|iPhoneSimulator
|
||||
AppStore|x64 = AppStore|x64
|
||||
AppStore|x86 = AppStore|x86
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|ARM = Debug|ARM
|
||||
Debug|iPhone = Debug|iPhone
|
||||
Debug|iPhoneSimulator = Debug|iPhoneSimulator
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
FDroid|Any CPU = FDroid|Any CPU
|
||||
FDroid|iPhone = FDroid|iPhone
|
||||
FDroid|iPhoneSimulator = FDroid|iPhoneSimulator
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|ARM = Release|ARM
|
||||
Release|iPhone = Release|iPhone
|
||||
Release|iPhoneSimulator = Release|iPhoneSimulator
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|ARM.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x64.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Ad-Hoc|x86.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|ARM.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x64.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.AppStore|x86.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Debug|ARM.Deploy.0 = Debug|Any CPU
|
||||
{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|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}.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
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|ARM.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x64.Deploy.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Build.0 = Release|Any CPU
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0}.Release|x86.Deploy.0 = Release|Any CPU
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||
{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|ARM.ActiveCfg = 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|x86.ActiveCfg = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Debug|x86.Build.0 = Debug|iPhone
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|Any CPU.ActiveCfg = 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
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{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
|
||||
{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
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x64.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.AppStore|x86.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{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|x86.ActiveCfg = Debug|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Debug|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
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A300DCE1-8D10-4267-B96A-CB01AEB7C220}.Release|x86.Build.0 = Release|Any CPU
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|ARM.ActiveCfg = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x64.ActiveCfg = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Ad-Hoc|x86.ActiveCfg = Ad-Hoc|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|ARM.ActiveCfg = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||
{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|ARM.ActiveCfg = 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|x86.ActiveCfg = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Debug|x86.Build.0 = Debug|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|Any CPU.ActiveCfg = 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
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x64.ActiveCfg = Release|iPhone
|
||||
{32F5A2D6-F54D-4DA1-AE26-0A980D48F422}.Release|x86.ActiveCfg = Release|iPhone
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x64.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Ad-Hoc|x86.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|ARM.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.AppStore|x64.Build.0 = Release|Any CPU
|
||||
{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|ARM.ActiveCfg = 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|x86.ActiveCfg = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|ARM.ActiveCfg = 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
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{B2538ADA-B605-4D6F-ACD2-62A409680F84}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{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
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|Any CPU.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|Any CPU.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|Any CPU.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|ARM.ActiveCfg = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|ARM.Build.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|ARM.Deploy.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhone.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhone.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhone.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x64.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x64.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x64.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x86.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x86.Build.0 = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Ad-Hoc|x86.Deploy.0 = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|Any CPU.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|Any CPU.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|Any CPU.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|ARM.ActiveCfg = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|ARM.Build.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|ARM.Deploy.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhone.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhone.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhone.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhoneSimulator.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhoneSimulator.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|iPhoneSimulator.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x64.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x64.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x64.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x86.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x86.Build.0 = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.AppStore|x86.Deploy.0 = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|ARM.ActiveCfg = Debug|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|ARM.Build.0 = Debug|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|ARM.Deploy.0 = Debug|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|iPhone.ActiveCfg = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|iPhoneSimulator.ActiveCfg = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x64.Build.0 = Debug|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x86.Build.0 = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|ARM.ActiveCfg = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|ARM.Build.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|ARM.Deploy.0 = Release|ARM
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|iPhone.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|iPhoneSimulator.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x64.ActiveCfg = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x64.Build.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x64.Deploy.0 = Release|x64
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x86.ActiveCfg = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x86.Build.0 = Release|x86
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141}.Release|x86.Deploy.0 = Release|x86
|
||||
{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}.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}.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
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhone.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhone.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.AppStore|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|Any CPU.Deploy.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhone.Deploy.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.FDroid|iPhoneSimulator.Deploy.0 = FDroid|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhone.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Ad-Hoc|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhone.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.AppStore|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhone.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Debug|iPhoneSimulator.Deploy.0 = Debug|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhone.Deploy.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C}.Release|iPhoneSimulator.Deploy.0 = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|Any CPU.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhone.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhoneSimulator.Build.0 = FDroid|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.ActiveCfg = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhone.Build.0 = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|Any CPU.ActiveCfg = AppStore|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhone.Build.0 = AppStore|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.ActiveCfg = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.ActiveCfg = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Build.0 = Debug|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|Any CPU.ActiveCfg = FDroid|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhone.ActiveCfg = FDroid|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhone.Build.0 = FDroid|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhoneSimulator.ActiveCfg = FDroid|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|iPhoneSimulator.Build.0 = FDroid|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|Any CPU.ActiveCfg = Release|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.ActiveCfg = Release|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhone.Build.0 = Release|iPhone
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{04B18ED2-B76D-4947-8474-191F8FD2B5E0} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{1F78403F-9A28-405B-9289-B9DBEB55F074} = {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}
|
||||
{304400AF-F0ED-40FA-B102-EA3C3EC43E4F} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{EE44C6A1-2A85-45FE-8D9B-BF1D5F88809C} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{4B8A8C41-9820-4341-974C-41E65B7F4366} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{9C8DA5A8-904D-466F-B9B0-1A4AB5A9AFC3} = {8904C536-C67D-420F-9971-51B26574C3AA}
|
||||
{2E399654-26A2-46F6-B9CA-1B496A3F370A} = {92470CBD-9047-4C3C-8EA3-D972D6622D84}
|
||||
{3A2D5669-ED71-4F2B-BA85-2D36BAA05141} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{0BE54BBB-7772-4289-BD51-1FDBB0CC2446} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{8A279EE4-4537-4656-9C93-44945E594556} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
|
||||
{D5D91152-CB01-4F24-A503-304D3A94408B} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
||||
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
|
||||
{E71F3053-056C-4381-9638-048ED73BDFF6} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
{599E0201-420A-4C3E-A7BA-5349F72E0B15} = {D10CA4A9-F866-40E1-B658-F69051236C71}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {318CB2DF-0118-43A3-AC83-56BADCF71CCD}
|
||||
SolutionGuid = {7D436EA3-8B7E-45D2-8D14-0730BD2E0410}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
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
|
||||
en-GB: en-GB
|
||||
- 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
|
||||
en-GB: en-GB
|
||||
- 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
|
||||
en-GB: en-GB
|
||||
|
||||
344
package-lock.json
generated
Normal file
@@ -0,0 +1,344 @@
|
||||
{
|
||||
"name": "bitwarden-fdroid",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
|
||||
"integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-uniq": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"array-uniq": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
|
||||
"integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=",
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
|
||||
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"commander": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
|
||||
"integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==",
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
|
||||
"dev": true
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"filename-reserved-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",
|
||||
"integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q=",
|
||||
"dev": true
|
||||
},
|
||||
"filenamify": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",
|
||||
"integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"filename-reserved-regex": "^1.0.0",
|
||||
"strip-outer": "^1.0.0",
|
||||
"trim-repeated": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"filenamify-url": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",
|
||||
"integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"filenamify": "^1.0.0",
|
||||
"humanize-url": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
|
||||
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.2",
|
||||
"jsonfile": "^4.0.0",
|
||||
"universalify": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
|
||||
"dev": true
|
||||
},
|
||||
"gh-pages": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-1.2.0.tgz",
|
||||
"integrity": "sha512-cGLYAvxtlQ1iTwAS4g7FreZPXoE/g62Fsxln2mmR19mgs4zZI+XJ+wVVUhBFCF/0+Nmvbq+abyTWue1m1BSnmg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "2.6.1",
|
||||
"commander": "2.15.1",
|
||||
"filenamify-url": "^1.0.0",
|
||||
"fs-extra": "^5.0.0",
|
||||
"globby": "^6.1.0",
|
||||
"graceful-fs": "4.1.11",
|
||||
"rimraf": "^2.6.2"
|
||||
}
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
|
||||
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"globby": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
|
||||
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"array-union": "^1.0.1",
|
||||
"glob": "^7.0.3",
|
||||
"object-assign": "^4.0.1",
|
||||
"pify": "^2.0.0",
|
||||
"pinkie-promise": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
|
||||
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=",
|
||||
"dev": true
|
||||
},
|
||||
"humanize-url": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
|
||||
"integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"normalize-url": "^1.0.0",
|
||||
"strip-url-auth": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
|
||||
"dev": true
|
||||
},
|
||||
"is-plain-obj": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
|
||||
"integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=",
|
||||
"dev": true
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
|
||||
"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
|
||||
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"normalize-url": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
|
||||
"integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-assign": "^4.0.1",
|
||||
"prepend-http": "^1.0.0",
|
||||
"query-string": "^4.1.0",
|
||||
"sort-keys": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||
"dev": true
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"dev": true
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
|
||||
"dev": true
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
|
||||
"integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
|
||||
"dev": true
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
|
||||
"integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"pinkie": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
|
||||
"integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=",
|
||||
"dev": true
|
||||
},
|
||||
"query-string": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz",
|
||||
"integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"object-assign": "^4.1.0",
|
||||
"strict-uri-encode": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"sort-keys": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",
|
||||
"integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-plain-obj": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"strict-uri-encode": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||
"dev": true
|
||||
},
|
||||
"strip-outer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
|
||||
"integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"strip-url-auth": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",
|
||||
"integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164=",
|
||||
"dev": true
|
||||
},
|
||||
"trim-repeated": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
|
||||
"integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"escape-string-regexp": "^1.0.2"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
11
package.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "bitwarden-mobile",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"deploy": "gh-pages --dist dist",
|
||||
"clean:l10n": "git push origin --delete l10n_master"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gh-pages": "^1.2.0"
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,16 @@ using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
using System;
|
||||
|
||||
namespace Bit.Android
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
[Activity(Theme = "@style/BitwardenTheme.Splash",
|
||||
Label = "bitwarden",
|
||||
Icon = "@drawable/icon",
|
||||
WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
public class AutofillActivity : Activity
|
||||
[Activity(Theme = "@style/LightTheme.Splash", WindowSoftInputMode = SoftInput.StateHidden)]
|
||||
public class AccessibilityActivity : Activity
|
||||
{
|
||||
private DateTime? _lastLaunch = null;
|
||||
private string _lastQueriedUri;
|
||||
|
||||
public static AutofillCredentials LastCredentials { get; set; }
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
base.OnCreate(bundle);
|
||||
@@ -41,7 +38,6 @@ namespace Bit.Android
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Intent.RemoveExtra("uri");
|
||||
}
|
||||
|
||||
@@ -50,7 +46,7 @@ namespace Bit.Android
|
||||
base.OnActivityResult(requestCode, resultCode, data);
|
||||
if(data == null)
|
||||
{
|
||||
LastCredentials = null;
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -58,15 +54,14 @@ namespace Bit.Android
|
||||
{
|
||||
if(data.GetStringExtra("canceled") != null)
|
||||
{
|
||||
LastCredentials = null;
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var uri = data.GetStringExtra("uri");
|
||||
var username = data.GetStringExtra("username");
|
||||
var password = data.GetStringExtra("password");
|
||||
|
||||
LastCredentials = new AutofillCredentials
|
||||
AccessibilityHelpers.LastCredentials = new Credentials
|
||||
{
|
||||
Username = username,
|
||||
Password = password,
|
||||
@@ -77,10 +72,9 @@ namespace Bit.Android
|
||||
}
|
||||
catch
|
||||
{
|
||||
LastCredentials = null;
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
}
|
||||
|
||||
Finish();
|
||||
}
|
||||
|
||||
@@ -92,7 +86,13 @@ namespace Bit.Android
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
var now = DateTime.UtcNow;
|
||||
if(_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_lastLaunch = now;
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
|
||||
{
|
||||
246
src/Android/Accessibility/AccessibilityHelpers.cs
Normal file
@@ -0,0 +1,246 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Android.OS;
|
||||
using Android.Views.Accessibility;
|
||||
using Bit.Core;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public static class AccessibilityHelpers
|
||||
{
|
||||
public static Credentials LastCredentials = null;
|
||||
public static string SystemUiPackage = "com.android.systemui";
|
||||
public static string BitwardenTag = "bw_access";
|
||||
|
||||
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
|
||||
{
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
new Browser("com.chrome.beta", "url_bar"),
|
||||
new Browser("org.chromium.chrome", "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.opera.touch", "addressbarEdit"),
|
||||
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[]{' ', ' '}).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.fennec_aurora", "url_bar_title"),
|
||||
new Browser("org.mozilla.focus", "display_url"),
|
||||
new Browser("org.mozilla.klar", "display_url"),
|
||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
|
||||
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"),
|
||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||
new Browser("com.ecosia.android", "url_bar"),
|
||||
new Browser("com.qwant.liberty", "url_bar_title"),
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
// Known packages to skip
|
||||
public 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"
|
||||
};
|
||||
|
||||
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
|
||||
{
|
||||
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 });
|
||||
}
|
||||
|
||||
public static string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
||||
if(SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
var browser = SupportedBrowsers[root.PackageName];
|
||||
var addressNode = root.FindAccessibilityNodeInfosByViewId(
|
||||
$"{root.PackageName}:id/{browser.UriViewId}").FirstOrDefault();
|
||||
if(addressNode != null)
|
||||
{
|
||||
uri = ExtractUri(uri, addressNode, browser);
|
||||
addressNode.Dispose();
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
public static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
|
||||
{
|
||||
if(addressNode?.Text == null)
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
if(addressNode.Text == null)
|
||||
{
|
||||
return uri;
|
||||
}
|
||||
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>
|
||||
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
|
||||
{
|
||||
if(credentials == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
|
||||
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
|
||||
{
|
||||
return lastUri.Host == currentUri.Host;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool EditText(AccessibilityNodeInfo n)
|
||||
{
|
||||
return n?.ClassName?.Contains("EditText") ?? false;
|
||||
}
|
||||
|
||||
|
||||
public static void FillCredentials(AccessibilityNodeInfo usernameNode,
|
||||
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
FillEditText(usernameNode, LastCredentials?.Username);
|
||||
foreach(var n in passwordNodes)
|
||||
{
|
||||
FillEditText(n, LastCredentials?.Password);
|
||||
}
|
||||
}
|
||||
|
||||
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
|
||||
{
|
||||
if(editTextNode == null || value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var bundle = new Bundle();
|
||||
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
|
||||
editTextNode.PerformAction(Android.Views.Accessibility.Action.SetText, bundle);
|
||||
}
|
||||
|
||||
public static 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)
|
||||
{
|
||||
var add = n.WindowId == e.WindowId &&
|
||||
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
|
||||
condition(n);
|
||||
if(add)
|
||||
{
|
||||
dispose = false;
|
||||
nodes.Add(n);
|
||||
}
|
||||
|
||||
for(var i = 0; i < n.ChildCount; i++)
|
||||
{
|
||||
var childNode = n.GetChild(i);
|
||||
if(i > 100)
|
||||
{
|
||||
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
|
||||
break;
|
||||
}
|
||||
else if(childNode.GetHashCode() == n.GetHashCode())
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||
IEnumerable<AccessibilityNodeInfo> passwordNodes)
|
||||
{
|
||||
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
|
||||
var usernameEditText = GetUsernameEditText(allEditTexts);
|
||||
FillCredentials(usernameEditText, passwordNodes);
|
||||
allEditTexts.Dispose();
|
||||
usernameEditText = null;
|
||||
}
|
||||
|
||||
public static AccessibilityNodeInfo GetUsernameEditText(IEnumerable<AccessibilityNodeInfo> allEditTexts)
|
||||
{
|
||||
return allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
310
src/Android/Accessibility/AccessibilityService.cs
Normal file
@@ -0,0 +1,310 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Android.Views.Accessibility;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
||||
public class AccessibilityService : Android.AccessibilityServices.AccessibilityService
|
||||
{
|
||||
private NotificationChannel _notificationChannel;
|
||||
|
||||
private const int AutoFillNotificationId = 34573;
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "vault.bitwarden.com";
|
||||
|
||||
private IStorageService _storageService;
|
||||
private bool _settingAutofillPasswordField;
|
||||
private bool _settingAutofillPersistNotification;
|
||||
private DateTime? _lastSettingsReload = null;
|
||||
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
|
||||
private long _lastNotificationTime = 0;
|
||||
private string _lastNotificationUri = null;
|
||||
private HashSet<string> _launcherPackageNames = null;
|
||||
private DateTime? _lastLauncherSetBuilt = null;
|
||||
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
|
||||
|
||||
public override void OnAccessibilityEvent(AccessibilityEvent e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var powerManager = GetSystemService(PowerService) as PowerManager;
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(SkipPackage(e?.PackageName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var root = RootInActiveWindow;
|
||||
if(root == null || root.PackageName != e.PackageName)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// AccessibilityHelpers.PrintTestData(root, e);
|
||||
LoadServices();
|
||||
var settingsTask = LoadSettingsAsync();
|
||||
|
||||
var notificationManager = GetSystemService(NotificationService) as NotificationManager;
|
||||
var cancelNotification = true;
|
||||
|
||||
switch(e.EventType)
|
||||
{
|
||||
case EventTypes.ViewFocused:
|
||||
if(e.Source == null || !e.Source.Password || !_settingAutofillPasswordField)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
if(ScanAndAutofill(root, e, notificationManager, cancelNotification))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
break;
|
||||
case EventTypes.WindowContentChanged:
|
||||
case EventTypes.WindowStateChanged:
|
||||
if(_settingAutofillPasswordField && e.Source.Password)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if(_settingAutofillPasswordField && AccessibilityHelpers.LastCredentials == null)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(_lastNotificationUri))
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != _lastNotificationUri)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
}
|
||||
else if(uri.StartsWith(Constants.AndroidAppProtocol))
|
||||
{
|
||||
CancelNotification(notificationManager, 30000);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if(e.PackageName == BitwardenPackage)
|
||||
{
|
||||
CancelNotification(notificationManager);
|
||||
break;
|
||||
}
|
||||
|
||||
if(_settingAutofillPersistNotification)
|
||||
{
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
var needToFill = AccessibilityHelpers.NeedToAutofill(
|
||||
AccessibilityHelpers.LastCredentials, uri);
|
||||
if(needToFill)
|
||||
{
|
||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e,
|
||||
n => n.Password, false);
|
||||
needToFill = passwordNodes.Any();
|
||||
if(needToFill)
|
||||
{
|
||||
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||
}
|
||||
passwordNodes.Dispose();
|
||||
}
|
||||
if(!needToFill)
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
AccessibilityHelpers.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()
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e,
|
||||
NotificationManager notificationManager, bool cancelNotification)
|
||||
{
|
||||
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
|
||||
if(passwordNodes.Count > 0)
|
||||
{
|
||||
var uri = AccessibilityHelpers.GetUri(root);
|
||||
if(uri != null && !uri.Contains(BitwardenWebsite))
|
||||
{
|
||||
if(AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
|
||||
{
|
||||
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
|
||||
}
|
||||
else
|
||||
{
|
||||
NotifyToAutofill(uri, notificationManager);
|
||||
cancelNotification = false;
|
||||
}
|
||||
}
|
||||
AccessibilityHelpers.LastCredentials = null;
|
||||
}
|
||||
else if(AccessibilityHelpers.LastCredentials != null)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
AccessibilityHelpers.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 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(AccessibilityActivity));
|
||||
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.shield)
|
||||
.SetContentTitle(AppResources.BitwardenAutofillService)
|
||||
.SetContentText(notificationContent)
|
||||
.SetTicker(notificationContent)
|
||||
.SetWhen(now)
|
||||
.SetContentIntent(pendingIntent);
|
||||
|
||||
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
|
||||
{
|
||||
builder.SetVisibility(NotificationVisibility.Secret)
|
||||
.SetColor(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 && */_settingAutofillPersistNotification)
|
||||
{
|
||||
builder.SetPriority(-2);
|
||||
}
|
||||
|
||||
_lastNotificationTime = now;
|
||||
_lastNotificationUri = uri;
|
||||
notificationManager.Notify(AutoFillNotificationId, builder.Build());
|
||||
builder.Dispose();
|
||||
}
|
||||
|
||||
private bool SkipPackage(string eventPackageName)
|
||||
{
|
||||
if(string.IsNullOrWhiteSpace(eventPackageName) ||
|
||||
AccessibilityHelpers.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);
|
||||
}
|
||||
|
||||
private void LoadServices()
|
||||
{
|
||||
if(_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LoadSettingsAsync()
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
if(_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
|
||||
{
|
||||
_lastSettingsReload = now;
|
||||
_settingAutofillPasswordField = await _storageService.GetAsync<bool>(
|
||||
Constants.AccessibilityAutofillPasswordFieldKey);
|
||||
_settingAutofillPersistNotification = await _storageService.GetAsync<bool>(
|
||||
Constants.AccessibilityAutofillPersistNotificationKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
src/Android/Accessibility/Browser.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace Bit.Android
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public class AutofillCredentials
|
||||
public class Credentials
|
||||
{
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
17
src/Android/Accessibility/NodeList.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Android.Views.Accessibility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
public class NodeList : List<AccessibilityNodeInfo>, IDisposable
|
||||
{
|
||||
public void Dispose()
|
||||
{
|
||||
foreach(var item in this)
|
||||
{
|
||||
item.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
Any raw assets you want to be deployed with your application can be placed in
|
||||
this directory (and child directories) and given a Build Action of "AndroidAsset".
|
||||
|
||||
These files will be deployed with you package and will be accessible using Android's
|
||||
AssetManager, like this:
|
||||
|
||||
public class ReadAsset : Activity
|
||||
{
|
||||
protected override void OnCreate (Bundle bundle)
|
||||
{
|
||||
base.OnCreate (bundle);
|
||||
|
||||
InputStream input = Assets.Open ("my_asset.txt");
|
||||
}
|
||||
}
|
||||
|
||||
Additionally, some Android functions will automatically load asset files:
|
||||
|
||||
Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
|
||||
BIN
src/Android/Assets/FontAwesome.ttf
Normal file
BIN
src/Android/Assets/MaterialIcons_Regular.ttf
Normal file
BIN
src/Android/Assets/RobotoMono_Regular.ttf
Normal file
@@ -1,68 +1,110 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 Bit.Core.Enums;
|
||||
using Android.Views.Autofill;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public static class AutofillHelpers
|
||||
{
|
||||
private static int _pendingIntentId = 0;
|
||||
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService service)
|
||||
// These browser work natively with the autofill framework
|
||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
||||
{
|
||||
var items = new List<FilledItem>();
|
||||
"org.mozilla.focus",
|
||||
"org.mozilla.klar",
|
||||
"com.duckduckgo.mobile.android",
|
||||
};
|
||||
|
||||
// These browsers work using the compatibility shim for the autofill framework
|
||||
public static HashSet<string> CompatBrowsers = new HashSet<string>
|
||||
{
|
||||
"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",
|
||||
"mark.via.gp",
|
||||
"org.bromite.bromite",
|
||||
"org.chromium.chrome",
|
||||
"com.kiwibrowser.browser",
|
||||
"com.ecosia.android",
|
||||
"com.opera.mini.native.beta",
|
||||
"org.mozilla.fennec_aurora",
|
||||
"com.qwant.liberty",
|
||||
"com.opera.touch",
|
||||
"org.mozilla.fenix",
|
||||
"org.mozilla.reference.browser",
|
||||
"org.mozilla.rocket",
|
||||
};
|
||||
|
||||
// The URLs are blacklisted from autofilling
|
||||
public static HashSet<string> BlacklistedUris = new HashSet<string>
|
||||
{
|
||||
"androidapp://android",
|
||||
"androidapp://com.x8bit.bitwarden",
|
||||
"androidapp://com.oneplus.applocker",
|
||||
};
|
||||
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
|
||||
{
|
||||
if(parser.FieldCollection.FillableForLogin)
|
||||
{
|
||||
var ciphers = await service.GetAllAsync(parser.Uri);
|
||||
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(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));
|
||||
}
|
||||
return allCiphers.Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
}
|
||||
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));
|
||||
}
|
||||
var ciphers = await cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
|
||||
return items;
|
||||
return new List<FilledItem>();
|
||||
}
|
||||
|
||||
public static FillResponse BuildFillResponse(Context context, Parser parser, List<FilledItem> items, bool locked)
|
||||
public static FillResponse BuildFillResponse(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);
|
||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, item);
|
||||
if(dataset != null)
|
||||
{
|
||||
responseBuilder.AddDataset(dataset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
responseBuilder.AddDataset(BuildVaultDataset(context, parser.FieldCollection, parser.Uri, locked));
|
||||
AddSaveInfo(responseBuilder, parser.FieldCollection);
|
||||
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
|
||||
parser.Uri, locked));
|
||||
AddSaveInfo(parser, responseBuilder, parser.FieldCollection);
|
||||
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
|
||||
return responseBuilder.Build();
|
||||
}
|
||||
@@ -70,7 +112,7 @@ namespace Bit.Android.Autofill
|
||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
|
||||
{
|
||||
var datasetBuilder = new Dataset.Builder(
|
||||
BuildListView(context.PackageName, filledItem.Name, filledItem.Subtitle, filledItem.Icon));
|
||||
BuildListView(filledItem.Name, filledItem.Subtitle, filledItem.Icon, context));
|
||||
if(filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
{
|
||||
return datasetBuilder.Build();
|
||||
@@ -102,8 +144,11 @@ namespace Bit.Android.Autofill
|
||||
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 view = BuildListView(
|
||||
AppResources.AutofillWithBitwarden,
|
||||
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault,
|
||||
Resource.Drawable.icon,
|
||||
context);
|
||||
|
||||
var datasetBuilder = new Dataset.Builder(view);
|
||||
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
|
||||
@@ -113,21 +158,29 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
|
||||
}
|
||||
|
||||
return datasetBuilder.Build();
|
||||
}
|
||||
|
||||
public static RemoteViews BuildListView(string packageName, string text, string subtext, int iconId)
|
||||
public static RemoteViews BuildListView(string text, string subtext, int iconId, Context context)
|
||||
{
|
||||
var packageName = context.PackageName;
|
||||
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
|
||||
view.SetTextViewText(Resource.Id.text, text);
|
||||
view.SetTextViewText(Resource.Id.text1, text);
|
||||
view.SetTextViewText(Resource.Id.text2, subtext);
|
||||
view.SetImageViewResource(Resource.Id.icon, iconId);
|
||||
return view;
|
||||
}
|
||||
|
||||
public static void AddSaveInfo(FillResponse.Builder responseBuilder, FieldCollection fields)
|
||||
public static void AddSaveInfo(Parser parser, FillResponse.Builder responseBuilder, FieldCollection fields)
|
||||
{
|
||||
// Docs state that password fields cannot be reliably saved in Compat mode since they will show as
|
||||
// masked values.
|
||||
var compatBrowser = CompatBrowsers.Contains(parser.PackageName);
|
||||
if(compatBrowser && fields.SaveType == SaveDataType.Password)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var requiredIds = fields.GetRequiredSaveFields();
|
||||
if(fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
|
||||
{
|
||||
@@ -140,7 +193,11 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
saveBuilder.SetOptionalIds(optionalIds);
|
||||
}
|
||||
if(compatBrowser)
|
||||
{
|
||||
saveBuilder.SetFlags(SaveFlags.SaveOnAllViewsInvisible);
|
||||
}
|
||||
responseBuilder.SetSaveInfo(saveBuilder.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,25 +5,27 @@ 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 Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "bitwarden")]
|
||||
[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
|
||||
public class AutofillService : Android.Service.Autofill.AutofillService
|
||||
{
|
||||
private ICipherService _cipherService;
|
||||
private ILockService _lockService;
|
||||
private IStorageService _storageService;
|
||||
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal, FillCallback callback)
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
||||
FillCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
@@ -31,38 +33,42 @@ namespace Bit.Android.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(structure);
|
||||
var parser = new Parser(structure, ApplicationContext);
|
||||
parser.Parse();
|
||||
|
||||
if(string.IsNullOrWhiteSpace(parser.Uri) || parser.Uri == "androidapp://com.x8bit.bitwarden" ||
|
||||
parser.Uri == "androidapp://android" || !parser.FieldCollection.Fillable)
|
||||
if(_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
|
||||
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
|
||||
if(!shouldAutofill)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_lockService == null)
|
||||
{
|
||||
_lockService = Resolver.Resolve<ILockService>();
|
||||
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
}
|
||||
|
||||
List<FilledItem> items = null;
|
||||
var locked = (await _lockService.GetLockTypeAsync(false)) != LockType.None;
|
||||
var locked = await _lockService.IsLockedAsync();
|
||||
if(!locked)
|
||||
{
|
||||
if(_cipherService == null)
|
||||
{
|
||||
_cipherService = Resolver.Resolve<ICipherService>();
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
}
|
||||
|
||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
||||
}
|
||||
|
||||
// build response
|
||||
var response = AutofillHelpers.BuildFillResponse(this, parser, items, locked);
|
||||
var response = AutofillHelpers.BuildFillResponse(parser, items, locked);
|
||||
callback.OnSuccess(response);
|
||||
}
|
||||
|
||||
public override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
||||
public async override void OnSaveRequest(SaveRequest request, SaveCallback callback)
|
||||
{
|
||||
var structure = request.FillContexts?.LastOrDefault()?.Structure;
|
||||
if(structure == null)
|
||||
@@ -70,7 +76,18 @@ namespace Bit.Android.Autofill
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(structure);
|
||||
if(_storageService == null)
|
||||
{
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
}
|
||||
|
||||
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
|
||||
if(disableSavePrompt.GetValueOrDefault())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var parser = new Parser(structure, ApplicationContext);
|
||||
parser.Parse();
|
||||
|
||||
var savedItem = parser.FieldCollection.GetSavedItem();
|
||||
|
||||
@@ -7,7 +7,7 @@ using static Android.App.Assist.AssistStructure;
|
||||
using Android.Text;
|
||||
using static Android.Views.ViewStructure;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class Field
|
||||
{
|
||||
@@ -16,6 +16,7 @@ namespace Bit.Android.Autofill
|
||||
public Field(ViewNode node)
|
||||
{
|
||||
Id = node.Id;
|
||||
TrackingId = $"{node.Id}_{node.GetHashCode()}";
|
||||
IdEntry = node.IdEntry;
|
||||
AutofillId = node.AutofillId;
|
||||
AutofillType = node.AutofillType;
|
||||
@@ -68,6 +69,7 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
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; }
|
||||
@@ -84,6 +86,69 @@ namespace Bit.Android.Autofill
|
||||
public HtmlInfo HtmlInfo { get; private set; }
|
||||
public ViewNode Node { get; private set; }
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSaveTypeFromHints()
|
||||
{
|
||||
SaveType = SaveDataType.Generic;
|
||||
@@ -126,72 +191,5 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,14 +5,15 @@ using System.Linq;
|
||||
using Android.Text;
|
||||
using Android.Views;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.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 HashSet<int> Ids { get; private set; } = new HashSet<int>();
|
||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
||||
public SaveDataType SaveType
|
||||
{
|
||||
@@ -32,9 +33,8 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
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<int, Field> IdToFieldMap { get; private set; } =
|
||||
new Dictionary<int, 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>();
|
||||
@@ -47,7 +47,6 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
return _passwordFields;
|
||||
}
|
||||
|
||||
if(Hints.Any())
|
||||
{
|
||||
_passwordFields = new List<Field>();
|
||||
@@ -58,24 +57,12 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordFields = Fields
|
||||
.Where(f =>
|
||||
(!f.IdEntry?.ToLowerInvariant().Contains("search") ?? true) &&
|
||||
(!f.Hint?.ToLowerInvariant().Contains("search") ?? true) &&
|
||||
(
|
||||
f.InputType.HasFlag(InputTypes.TextVariationPassword) ||
|
||||
f.InputType.HasFlag(InputTypes.TextVariationVisiblePassword) ||
|
||||
f.InputType.HasFlag(InputTypes.TextVariationWebPassword)
|
||||
)
|
||||
).ToList();
|
||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||
if(!_passwordFields.Any())
|
||||
{
|
||||
_passwordFields = Fields.Where(f =>
|
||||
(f.IdEntry?.ToLowerInvariant().Contains("password") ?? false)
|
||||
|| (f.Hint?.ToLowerInvariant().Contains("password") ?? false)).ToList();
|
||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
return _passwordFields;
|
||||
}
|
||||
}
|
||||
@@ -88,7 +75,6 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
return _usernameFields;
|
||||
}
|
||||
|
||||
_usernameFields = new List<Field>();
|
||||
if(Hints.Any())
|
||||
{
|
||||
@@ -105,45 +91,49 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
foreach(var passwordField in PasswordFields)
|
||||
{
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId).LastOrDefault();
|
||||
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 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(Ids.Contains(field.Id))
|
||||
if(field == null || FieldTrackingIds.Contains(field.TrackingId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_passwordFields = _usernameFields = null;
|
||||
|
||||
if(field.Id > -1)
|
||||
{
|
||||
Ids.Add(field.Id);
|
||||
IdToFieldMap.Add(field.Id, field);
|
||||
}
|
||||
|
||||
FieldTrackingIds.Add(field.TrackingId);
|
||||
Fields.Add(field);
|
||||
AutofillIds.Add(field.AutofillId);
|
||||
|
||||
@@ -156,12 +146,10 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
FocusedHints.Add(hint);
|
||||
}
|
||||
|
||||
if(!HintToFieldsMap.ContainsKey(hint))
|
||||
{
|
||||
HintToFieldsMap.Add(hint, new List<Field>());
|
||||
}
|
||||
|
||||
HintToFieldsMap[hint].Add(field);
|
||||
}
|
||||
}
|
||||
@@ -179,7 +167,7 @@ namespace Bit.Android.Autofill
|
||||
|
||||
var savedItem = new SavedItem
|
||||
{
|
||||
Type = App.Enums.CipherType.Login,
|
||||
Type = Core.Enums.CipherType.Login,
|
||||
Login = new SavedItem.LoginItem
|
||||
{
|
||||
Password = GetFieldValue(passwordField)
|
||||
@@ -188,14 +176,13 @@ namespace Bit.Android.Autofill
|
||||
|
||||
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,
|
||||
Type = Core.Enums.CipherType.Card,
|
||||
Card = new SavedItem.CardItem
|
||||
{
|
||||
Number = GetFieldValue(View.AutofillHintCreditCardNumber),
|
||||
@@ -205,10 +192,8 @@ namespace Bit.Android.Autofill
|
||||
Code = GetFieldValue(View.AutofillHintCreditCardSecurityCode)
|
||||
}
|
||||
};
|
||||
|
||||
return savedItem;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -239,7 +224,6 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
return fieldList.Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
|
||||
return new AutofillId[0];
|
||||
}
|
||||
|
||||
@@ -253,7 +237,6 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
|
||||
}
|
||||
|
||||
return new AutofillId[0];
|
||||
}
|
||||
|
||||
@@ -275,7 +258,6 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -285,7 +267,6 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!string.IsNullOrWhiteSpace(field.TextValue))
|
||||
{
|
||||
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
|
||||
@@ -309,8 +290,53 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
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);
|
||||
|
||||
// For whatever reason, multi-line input types are coming through with TextVariationPassword flags
|
||||
if(inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
|
||||
f.InputType.HasFlag(InputTypes.TextFlagMultiLine))
|
||||
{
|
||||
inputTypePassword = false;
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,107 +1,56 @@
|
||||
using System;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Service.Autofill;
|
||||
using Android.Views.Autofill;
|
||||
using System.Linq;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Android.Views;
|
||||
using Bit.Core.Models.View;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class FilledItem
|
||||
{
|
||||
private Lazy<string> _password;
|
||||
private Lazy<string> _cardName;
|
||||
private string _password;
|
||||
private 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;
|
||||
private string _cardExpMonth;
|
||||
private string _cardExpYear;
|
||||
private string _cardCode;
|
||||
private string _idPhone;
|
||||
private string _idEmail;
|
||||
private string _idUsername;
|
||||
private string _idAddress;
|
||||
private string _idPostalCode;
|
||||
|
||||
public FilledItem(Cipher cipher)
|
||||
public FilledItem(CipherView cipher)
|
||||
{
|
||||
Name = cipher.Name?.Decrypt(cipher.OrganizationId) ?? "--";
|
||||
Name = cipher.Name;
|
||||
Type = cipher.Type;
|
||||
Subtitle = cipher.SubTitle;
|
||||
|
||||
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));
|
||||
_password = cipher.Login.Password;
|
||||
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));
|
||||
}
|
||||
_cardNumber = cipher.Card.Number;
|
||||
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));
|
||||
_cardName = cipher.Card.CardholderName;
|
||||
_cardCode = cipher.Card.Code;
|
||||
_cardExpMonth = cipher.Card.ExpMonth;
|
||||
_cardExpYear = cipher.Card.ExpYear;
|
||||
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));
|
||||
_idPhone = cipher.Identity.Phone;
|
||||
_idEmail = cipher.Identity.Email;
|
||||
_idUsername = cipher.Identity.Username;
|
||||
_idAddress = cipher.Identity.FullAddress;
|
||||
_idPostalCode = cipher.Identity.PostalCode;
|
||||
break;
|
||||
default:
|
||||
Icon = Resource.Drawable.login;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -121,11 +70,11 @@ namespace Bit.Android.Autofill
|
||||
var setValues = false;
|
||||
if(Type == CipherType.Login)
|
||||
{
|
||||
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password.Value))
|
||||
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
|
||||
{
|
||||
foreach(var f in fieldCollection.PasswordFields)
|
||||
{
|
||||
var val = ApplyValue(f, _password.Value);
|
||||
var val = ApplyValue(f, _password);
|
||||
if(val != null)
|
||||
{
|
||||
setValues = true;
|
||||
@@ -133,7 +82,6 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
|
||||
{
|
||||
foreach(var f in fieldCollection.UsernameFields)
|
||||
@@ -149,68 +97,73 @@ namespace Bit.Android.Autofill
|
||||
}
|
||||
else if(Type == CipherType.Card)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardNumber,
|
||||
new Lazy<string>(() => _cardNumber)))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
|
||||
_cardNumber))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardSecurityCode, _cardCode))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
|
||||
_cardCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection,
|
||||
Android.Views.View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintCreditCardExpirationYear, _cardExpYear))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
|
||||
_cardExpYear))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, _cardName))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
}
|
||||
else if(Type == CipherType.Identity)
|
||||
{
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPhone, _idPhone))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintEmailAddress, _idEmail))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintUsername, _idUsername))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
|
||||
_idUsername))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalAddress, _idAddress))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
|
||||
_idAddress))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintPostalCode, _idPostalCode))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
|
||||
_idPostalCode))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, View.AutofillHintName, new Lazy<string>(() => Subtitle)))
|
||||
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
|
||||
{
|
||||
setValues = true;
|
||||
}
|
||||
}
|
||||
|
||||
return setValues;
|
||||
}
|
||||
|
||||
private static bool ApplyValue(Dataset.Builder builder, FieldCollection fieldCollection,
|
||||
string hint, Lazy<string> value, bool monthValue = false)
|
||||
string hint, string value, bool monthValue = false)
|
||||
{
|
||||
bool setValues = false;
|
||||
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value.Value))
|
||||
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
foreach(var f in fieldCollection.HintToFieldsMap[hint])
|
||||
{
|
||||
var val = ApplyValue(f, value.Value, monthValue);
|
||||
var val = ApplyValue(f, value, monthValue);
|
||||
if(val != null)
|
||||
{
|
||||
setValues = true;
|
||||
@@ -245,7 +198,6 @@ namespace Bit.Android.Autofill
|
||||
return AutofillValue.ForList(monthIndex - 1);
|
||||
}
|
||||
}
|
||||
|
||||
for(var i = 0; i < field.AutofillOptions.Count; i++)
|
||||
{
|
||||
if(field.AutofillOptions[i].Equals(value))
|
||||
@@ -266,7 +218,6 @@ namespace Bit.Android.Autofill
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
using static Android.App.Assist.AssistStructure;
|
||||
using Android.App.Assist;
|
||||
using Bit.App;
|
||||
using System.Collections.Generic;
|
||||
using Bit.Core;
|
||||
using Android.Content;
|
||||
using Bit.Core.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class Parser
|
||||
{
|
||||
public static HashSet<string> TrustedBrowsers = new HashSet<string>
|
||||
public static HashSet<string> _excludedPackageIds = 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"
|
||||
"android"
|
||||
};
|
||||
|
||||
private readonly AssistStructure _structure;
|
||||
private string _uri;
|
||||
private string _packageName;
|
||||
private string _webDomain;
|
||||
|
||||
public Parser(AssistStructure structure)
|
||||
public Parser(AssistStructure structure, Context applicationContext)
|
||||
{
|
||||
_structure = structure;
|
||||
ApplicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public Context ApplicationContext { get; set; }
|
||||
public FieldCollection FieldCollection { get; private set; } = new FieldCollection();
|
||||
|
||||
public string Uri
|
||||
{
|
||||
get
|
||||
@@ -36,12 +36,12 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
return _uri;
|
||||
}
|
||||
|
||||
if(string.IsNullOrWhiteSpace(WebDomain) && string.IsNullOrWhiteSpace(PackageName))
|
||||
var webDomainNull = string.IsNullOrWhiteSpace(WebDomain);
|
||||
if(webDomainNull && string.IsNullOrWhiteSpace(PackageName))
|
||||
{
|
||||
_uri = null;
|
||||
}
|
||||
else if(!string.IsNullOrWhiteSpace(WebDomain))
|
||||
else if(!webDomainNull)
|
||||
{
|
||||
_uri = string.Concat("http://", WebDomain);
|
||||
}
|
||||
@@ -49,10 +49,10 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
||||
}
|
||||
|
||||
return _uri;
|
||||
}
|
||||
}
|
||||
|
||||
public string PackageName
|
||||
{
|
||||
get => _packageName;
|
||||
@@ -62,10 +62,10 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
_packageName = _uri = null;
|
||||
}
|
||||
|
||||
_packageName = value;
|
||||
}
|
||||
}
|
||||
|
||||
public string WebDomain
|
||||
{
|
||||
get => _webDomain;
|
||||
@@ -75,11 +75,25 @@ namespace Bit.Android.Autofill
|
||||
{
|
||||
_webDomain = _uri = null;
|
||||
}
|
||||
|
||||
_webDomain = value;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> ShouldAutofillAsync(IStorageService storageService)
|
||||
{
|
||||
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
|
||||
FieldCollection != null && FieldCollection.Fillable;
|
||||
if(fillable)
|
||||
{
|
||||
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
|
||||
if(blacklistedUris != null && blacklistedUris.Count > 0)
|
||||
{
|
||||
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);
|
||||
}
|
||||
}
|
||||
return fillable;
|
||||
}
|
||||
|
||||
public void Parse()
|
||||
{
|
||||
for(var i = 0; i < _structure.WindowNodeCount; i++)
|
||||
@@ -87,32 +101,24 @@ namespace Bit.Android.Autofill
|
||||
var node = _structure.GetWindowNodeAt(i);
|
||||
ParseNode(node.RootViewNode);
|
||||
}
|
||||
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
|
||||
!AutofillHelpers.CompatBrowsers.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)
|
||||
{
|
||||
if(PackageName == null)
|
||||
{
|
||||
PackageName = node.IdPackage;
|
||||
}
|
||||
if(WebDomain == null && TrustedBrowsers.Contains(node.IdPackage))
|
||||
{
|
||||
WebDomain = node.WebDomain;
|
||||
}
|
||||
|
||||
FieldCollection.Add(new Field(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
if(WebDomain == null && TrustedBrowsers.Contains(node.IdPackage))
|
||||
{
|
||||
WebDomain = node.WebDomain;
|
||||
}
|
||||
|
||||
FieldCollection.IgnoreAutofillIds.Add(node.AutofillId);
|
||||
}
|
||||
|
||||
@@ -121,5 +127,18 @@ namespace Bit.Android.Autofill
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using Bit.App.Enums;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Android.Autofill
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class SavedItem
|
||||
{
|
||||
|
||||
@@ -1,473 +0,0 @@
|
||||
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 int AutoFillNotificationId = 34573;
|
||||
private const string SystemUiPackage = "com.android.systemui";
|
||||
private const string BitwardenPackage = "com.x8bit.bitwarden";
|
||||
private const string BitwardenWebsite = "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(' ').FirstOrDefault()),
|
||||
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("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")
|
||||
}.ToDictionary(n => n.PackageName);
|
||||
|
||||
private readonly IAppSettingsService _appSettings;
|
||||
private long _lastNotificationTime = 0;
|
||||
private string _lastNotificationUri = null;
|
||||
|
||||
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
|
||||
{
|
||||
var root = RootInActiveWindow;
|
||||
if(e == null || root == null || string.IsNullOrWhiteSpace(e.PackageName) ||
|
||||
e.PackageName == SystemUiPackage || e.PackageName.Contains("launcher") ||
|
||||
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.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)
|
||||
{
|
||||
if(nodes == null)
|
||||
{
|
||||
nodes = new NodeList();
|
||||
}
|
||||
|
||||
if(n != null)
|
||||
{
|
||||
var dispose = disposeIfUnused;
|
||||
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++)
|
||||
{
|
||||
GetWindowNodes(n.GetChild(i), e, condition, true, nodes);
|
||||
}
|
||||
|
||||
if(dispose)
|
||||
{
|
||||
n.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
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);
|
||||
|
||||
var view = (ExtendedEditor)Element;
|
||||
|
||||
SetBorder(view);
|
||||
SetScrollable();
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var view = (ExtendedEditor)Element;
|
||||
|
||||
if(e.PropertyName == ExtendedEditor.HasBorderProperty.PropertyName)
|
||||
{
|
||||
SetBorder(view);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBorder(ExtendedEditor view)
|
||||
{
|
||||
if(!view.HasBorder)
|
||||
{
|
||||
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,212 +0,0 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.Text;
|
||||
using Android.Text.Method;
|
||||
using Android.Views.InputMethods;
|
||||
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;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedEntry), typeof(ExtendedEntryRenderer))]
|
||||
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);
|
||||
|
||||
_view = (ExtendedEntry)Element;
|
||||
_isPassword = _view.IsPassword;
|
||||
|
||||
if(Control != null)
|
||||
{
|
||||
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);
|
||||
|
||||
// Editor Action is called when the return button is pressed
|
||||
Control.EditorAction += Control_EditorAction;
|
||||
|
||||
if(_view.DisableAutocapitalize)
|
||||
{
|
||||
Control.SetRawInputType(Control.InputType |= InputTypes.TextVariationEmailAddress);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var view = (ExtendedEntry)Element;
|
||||
|
||||
if(e.PropertyName == ExtendedEntry.HasBorderProperty.PropertyName
|
||||
|| e.PropertyName == ExtendedEntry.HasOnlyBottomBorderProperty.PropertyName
|
||||
|| e.PropertyName == ExtendedEntry.BottomBorderColorProperty.PropertyName)
|
||||
{
|
||||
SetBorder(view);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if(view.ReturnType.HasValue)
|
||||
{
|
||||
switch(view.ReturnType.Value)
|
||||
{
|
||||
case ReturnType.Go:
|
||||
Control.ImeOptions = ImeAction.Go;
|
||||
Control.SetImeActionLabel("Go", ImeAction.Go);
|
||||
break;
|
||||
case ReturnType.Next:
|
||||
Control.ImeOptions = ImeAction.Next;
|
||||
Control.SetImeActionLabel("Next", ImeAction.Next);
|
||||
break;
|
||||
case ReturnType.Search:
|
||||
Control.ImeOptions = ImeAction.Search;
|
||||
Control.SetImeActionLabel("Search", ImeAction.Search);
|
||||
break;
|
||||
case ReturnType.Send:
|
||||
Control.ImeOptions = ImeAction.Send;
|
||||
Control.SetImeActionLabel("Send", ImeAction.Send);
|
||||
break;
|
||||
default:
|
||||
Control.SetImeActionLabel("Done", ImeAction.Done);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBorder(ExtendedEntry view)
|
||||
{
|
||||
if(!view.HasBorder)
|
||||
{
|
||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||
}
|
||||
else
|
||||
{
|
||||
Control.SetBackgroundColor(view.BottomBorderColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
|
||||
private void SetMaxLength(ExtendedEntry view)
|
||||
{
|
||||
Control.SetFilters(new IInputFilter[] { new InputFilterLengthFilter(view.MaxLength) });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
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(ExtendedPicker), typeof(ExtendedPickerRenderer))]
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var view = (ExtendedPicker)Element;
|
||||
|
||||
if(e.PropertyName == ExtendedPicker.HasBorderProperty.PropertyName)
|
||||
{
|
||||
SetBorder(view);
|
||||
}
|
||||
else
|
||||
{
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == VisualElement.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
Control.SetBackgroundColor(view.BackgroundColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetBorder(ExtendedPicker view)
|
||||
{
|
||||
if(!view.HasBorder)
|
||||
{
|
||||
Control.SetBackgroundColor(global::Android.Graphics.Color.Transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
using Android.Content;
|
||||
using System.ComponentModel;
|
||||
using Android.Views;
|
||||
using Bit.Android.Controls;
|
||||
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 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) as SwitchCellView;
|
||||
var extendedCell = (ExtendedSwitchCell)item;
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
base.OnCellPropertyChanged(sender, args);
|
||||
|
||||
var cell = (ExtendedSwitchCell)Cell;
|
||||
|
||||
if(args.PropertyName == ExtendedSwitchCell.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,179 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
using Android.Content;
|
||||
using System.ComponentModel;
|
||||
using Android.Views;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
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
|
||||
{
|
||||
public class ExtendedTextCellRenderer : TextCellRenderer
|
||||
{
|
||||
protected AView View { get; private set; }
|
||||
|
||||
protected override AView GetCellCore(Cell item, AView convertView, ViewGroup parent, Context context)
|
||||
{
|
||||
var View = (BaseCellView)base.GetCellCore(item, convertView, parent, context);
|
||||
var extendedCell = (ExtendedTextCell)item;
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
if(extendedCell.BackgroundColor != Color.White)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||
}
|
||||
|
||||
if(extendedCell.ShowDisclousure)
|
||||
{
|
||||
var resourceId = Resource.Drawable.ion_chevron_right;
|
||||
if(!string.IsNullOrWhiteSpace(extendedCell.DisclousureImage))
|
||||
{
|
||||
var fileName = System.IO.Path.GetFileNameWithoutExtension(extendedCell.DisclousureImage);
|
||||
resourceId = context.Resources.GetIdentifier(fileName, "drawable", context.PackageName);
|
||||
}
|
||||
|
||||
var image = new DisclosureImage(context, extendedCell);
|
||||
image.SetImageResource(resourceId);
|
||||
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;
|
||||
}
|
||||
|
||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
base.OnCellPropertyChanged(sender, args);
|
||||
|
||||
var cell = (ExtendedTextCell)Cell;
|
||||
|
||||
if(args.PropertyName == ExtendedTextCell.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
public DisclosureImage(Context context, ExtendedTextCell cell) : base(context)
|
||||
{
|
||||
_cell = cell;
|
||||
}
|
||||
|
||||
public override bool OnTouchEvent(MotionEvent e)
|
||||
{
|
||||
switch(e.Action)
|
||||
{
|
||||
case MotionEventActions.Up:
|
||||
_cell.OnDisclousureTapped();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
using Android.Content;
|
||||
using System.ComponentModel;
|
||||
using Android.Views;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using AView = Android.Views.View;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedViewCell), typeof(ExtendedViewCellRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
{
|
||||
public class ExtendedViewCellRenderer : ViewCellRenderer
|
||||
{
|
||||
protected AView 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 extendedCell = (ExtendedViewCell)item;
|
||||
|
||||
if(View != null)
|
||||
{
|
||||
if(extendedCell.BackgroundColor != Color.White)
|
||||
{
|
||||
View.SetBackgroundColor(extendedCell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
else
|
||||
{
|
||||
View.SetBackgroundResource(Resource.Drawable.list_selector);
|
||||
}
|
||||
}
|
||||
|
||||
return View;
|
||||
}
|
||||
|
||||
protected override void OnCellPropertyChanged(object sender, PropertyChangedEventArgs args)
|
||||
{
|
||||
base.OnCellPropertyChanged(sender, args);
|
||||
|
||||
var cell = (ExtendedViewCell)Cell;
|
||||
|
||||
if(args.PropertyName == ExtendedViewCell.BackgroundColorProperty.PropertyName)
|
||||
{
|
||||
View.SetBackgroundColor(cell.BackgroundColor.ToAndroid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Android/Effects/FixedSizeEffect.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportEffect(typeof(FixedSizeEffect), "FixedSizeEffect")]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class FixedSizeEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(Element is Label label && Control is TextView textView)
|
||||
{
|
||||
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetached()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
26
src/Android/Effects/SelectableLabelEffect.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportEffect(typeof(SelectableLabelEffect), "SelectableLabelEffect")]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class SelectableLabelEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(Control is TextView textView)
|
||||
{
|
||||
textView.SetTextIsSelectable(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetached()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Android/Effects/TabBarEffect.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using Android.Support.Design.BottomNavigation;
|
||||
using Android.Support.Design.Widget;
|
||||
using Android.Views;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ResolutionGroupName("Bitwarden")]
|
||||
[assembly: ExportEffect(typeof(TabBarEffect), "TabBarEffect")]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class TabBarEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if(!(Container.GetChildAt(0) is ViewGroup layout))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
|
||||
{
|
||||
return;
|
||||
}
|
||||
bottomNavigationView.LabelVisibilityMode = LabelVisibilityMode.LabelVisibilityLabeled;
|
||||
}
|
||||
|
||||
protected override void OnDetached()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,185 +1,210 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.Views;
|
||||
using Android.Runtime;
|
||||
using Android.OS;
|
||||
using Bit.App.Abstractions;
|
||||
using XLabs.Ioc;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Plugin.Connectivity.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 Bit.Core;
|
||||
using System.Linq;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using System.IO;
|
||||
using System;
|
||||
using Android.Content;
|
||||
using Bit.Droid.Utilities;
|
||||
using Bit.Droid.Receivers;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Android.Nfc;
|
||||
using Bit.App.Utilities;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Bit.Android
|
||||
namespace Bit.Droid
|
||||
{
|
||||
[Activity(Label = "bitwarden",
|
||||
Icon = "@drawable/icon",
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation,
|
||||
Exported = false)]
|
||||
public class MainActivity : FormsAppCompatActivity
|
||||
[Activity(
|
||||
Label = "Bitwarden",
|
||||
Icon = "@mipmap/ic_launcher",
|
||||
Theme = "@style/LightTheme.Splash",
|
||||
MainLauncher = true,
|
||||
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
|
||||
[Register("com.x8bit.bitwarden.MainActivity")]
|
||||
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||
{
|
||||
private const string HockeyAppId = "d3834185b4a643479047b86c65293d42";
|
||||
private Java.Util.Regex.Pattern _otpPattern = Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private ISettings _settings;
|
||||
private AppOptions _appOptions;
|
||||
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IMessagingService _messagingService;
|
||||
private IBroadcasterService _broadcasterService;
|
||||
private IUserService _userService;
|
||||
private IAppIdService _appIdService;
|
||||
private IStorageService _storageService;
|
||||
private IStateService _stateService;
|
||||
private PendingIntent _lockAlarmPendingIntent;
|
||||
private PendingIntent _clearClipboardPendingIntent;
|
||||
private AppOptions _appOptions;
|
||||
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
|
||||
private Java.Util.Regex.Pattern _otpPattern =
|
||||
Java.Util.Regex.Pattern.Compile("^.*?([cbdefghijklnrtuv]{32,64})$");
|
||||
|
||||
protected override void OnCreate(Bundle savedInstanceState)
|
||||
{
|
||||
if(!Resolver.IsSet)
|
||||
{
|
||||
MainApplication.SetIoc(Application);
|
||||
}
|
||||
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
|
||||
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
|
||||
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
|
||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
||||
StrictMode.SetThreadPolicy(policy);
|
||||
|
||||
ToolbarResource = Resource.Layout.toolbar;
|
||||
TabLayoutResource = Resource.Layout.tabs;
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_userService = ServiceContainer.Resolve<IUserService>("userService");
|
||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
|
||||
base.OnCreate(bundle);
|
||||
TabLayoutResource = Resource.Layout.Tabbar;
|
||||
ToolbarResource = Resource.Layout.Toolbar;
|
||||
|
||||
// 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())
|
||||
UpdateTheme(ThemeManager.GetTheme());
|
||||
base.OnCreate(savedInstanceState);
|
||||
if(!CoreHelpers.InDebugMode())
|
||||
{
|
||||
Window.AddFlags(WindowManagerFlags.Secure);
|
||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||
}
|
||||
|
||||
var appIdService = Resolver.Resolve<IAppIdService>();
|
||||
var authService = Resolver.Resolve<IAuthService>();
|
||||
#if !FDROID
|
||||
var hockeyAppListener = new HockeyAppCrashManagerListener(_appIdService, _userService);
|
||||
var hockeyAppTask = hockeyAppListener.InitAsync();
|
||||
HockeyApp.Android.CrashManager.Register(this, HockeyAppId, hockeyAppListener);
|
||||
#endif
|
||||
|
||||
HockeyApp.Android.CrashManager.Register(this, HockeyAppId,
|
||||
new HockeyAppCrashManagerListener(appIdService, authService));
|
||||
|
||||
Forms.Init(this, bundle);
|
||||
|
||||
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
|
||||
.SetValue(null, Color.FromHex("d2d6de"));
|
||||
|
||||
_deviceActionService = Resolver.Resolve<IDeviceActionService>();
|
||||
_settings = Resolver.Resolve<ISettings>();
|
||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
||||
_appOptions = GetOptions();
|
||||
LoadApplication(new App.App(
|
||||
_appOptions,
|
||||
Resolver.Resolve<IAuthService>(),
|
||||
Resolver.Resolve<IConnectivity>(),
|
||||
Resolver.Resolve<IDatabaseService>(),
|
||||
Resolver.Resolve<ISyncService>(),
|
||||
_settings,
|
||||
Resolver.Resolve<ILockService>(),
|
||||
Resolver.Resolve<ILocalizeService>(),
|
||||
Resolver.Resolve<IAppInfoService>(),
|
||||
Resolver.Resolve<IAppSettingsService>(),
|
||||
_deviceActionService));
|
||||
LoadApplication(new App.App(_appOptions));
|
||||
|
||||
if(_appOptions?.Uri == null)
|
||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||
{
|
||||
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());
|
||||
}
|
||||
if(message.Command == "scheduleLockTimer")
|
||||
{
|
||||
var lockOptionMinutes = (int)message.Data;
|
||||
var lockOptionMs = lockOptionMinutes * 60000;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
|
||||
}
|
||||
else if(message.Command == "cancelLockTimer")
|
||||
{
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Cancel(_lockAlarmPendingIntent);
|
||||
}
|
||||
else if(message.Command == "finishMainActivity")
|
||||
{
|
||||
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
|
||||
}
|
||||
else if(message.Command == "listenYubiKeyOTP")
|
||||
{
|
||||
ListenYubiKey((bool)message.Data);
|
||||
}
|
||||
else if(message.Command == "updatedTheme")
|
||||
{
|
||||
RestartApp();
|
||||
}
|
||||
else if(message.Command == "exit")
|
||||
{
|
||||
ExitApp();
|
||||
}
|
||||
else if(message.Command == "copiedToClipboard")
|
||||
{
|
||||
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void OnPause()
|
||||
{
|
||||
Console.WriteLine("A OnPause");
|
||||
base.OnPause();
|
||||
ListenYubiKey(false);
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
Console.WriteLine("A OnDestroy");
|
||||
base.OnDestroy();
|
||||
}
|
||||
|
||||
protected override void OnRestart()
|
||||
{
|
||||
Console.WriteLine("A OnRestart");
|
||||
base.OnRestart();
|
||||
}
|
||||
|
||||
protected override void OnStart()
|
||||
{
|
||||
Console.WriteLine("A OnStart");
|
||||
base.OnStart();
|
||||
}
|
||||
|
||||
protected override void OnStop()
|
||||
{
|
||||
Console.WriteLine("A OnStop");
|
||||
base.OnStop();
|
||||
}
|
||||
|
||||
protected override void 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(Utilities.NfcEnabled())
|
||||
if(_deviceActionService.SupportsNfc())
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "ResumeYubiKey");
|
||||
try
|
||||
{
|
||||
_messagingService.Send("resumeYubiKey");
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
{
|
||||
base.OnNewIntent(intent);
|
||||
Console.WriteLine("A OnNewIntent");
|
||||
ParseYubiKey(intent.DataString);
|
||||
if(intent.GetBooleanExtra("generatorTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabGenerator");
|
||||
if(_appOptions != null)
|
||||
{
|
||||
_appOptions.GeneratorTile = true;
|
||||
}
|
||||
}
|
||||
if(intent.GetBooleanExtra("myVaultTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabMyVault");
|
||||
if(_appOptions != null)
|
||||
{
|
||||
_appOptions.MyVaultTile = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ParseYubiKey(intent.DataString);
|
||||
}
|
||||
}
|
||||
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions, Permission[] grantResults)
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
||||
[GeneratedEnum] Permission[] grantResults)
|
||||
{
|
||||
if(requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
{
|
||||
if(grantResults.Any(r => r != Permission.Granted))
|
||||
{
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileCameraPermissionDenied");
|
||||
_messagingService.Send("selectFileCameraPermissionDenied");
|
||||
}
|
||||
await _deviceActionService.SelectFileAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
else
|
||||
{
|
||||
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(
|
||||
requestCode, permissions, grantResults);
|
||||
}
|
||||
base.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;
|
||||
Android.Net.Uri uri = null;
|
||||
string fileName = null;
|
||||
if(data != null && data.Data != null)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = Utilities.GetFileName(ApplicationContext, uri);
|
||||
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
// camera
|
||||
var root = new Java.IO.File(global::Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||
var root = new Java.IO.File(Android.OS.Environment.ExternalStorageDirectory, "bitwarden");
|
||||
var file = new Java.IO.File(root, "temp_camera_photo.jpg");
|
||||
uri = global::Android.Net.Uri.FromFile(file);
|
||||
uri = Android.Net.Uri.FromFile(file);
|
||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||
}
|
||||
|
||||
@@ -187,58 +212,60 @@ namespace Bit.Android
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||
using(var memoryStream = new MemoryStream())
|
||||
try
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
MessagingCenter.Send(Xamarin.Forms.Application.Current, "SelectFileResult",
|
||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||
using(var stream = ContentResolver.OpenInputStream(uri))
|
||||
using(var memoryStream = new MemoryStream())
|
||||
{
|
||||
stream.CopyTo(memoryStream);
|
||||
_messagingService.Send("selectFileResult",
|
||||
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
|
||||
}
|
||||
}
|
||||
catch(Java.IO.FileNotFoundException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy()
|
||||
{
|
||||
base.OnDestroy();
|
||||
_broadcasterService.Unsubscribe(_activityKey);
|
||||
}
|
||||
|
||||
private void ListenYubiKey(bool listen)
|
||||
{
|
||||
if(!Utilities.NfcEnabled())
|
||||
if(!_deviceActionService.SupportsNfc())
|
||||
{
|
||||
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);
|
||||
try
|
||||
{
|
||||
// register for foreground dispatch so we'll receive tags according to our intent filters
|
||||
adapter.EnableForegroundDispatch(this, pendingIntent, filters, null);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
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);
|
||||
try
|
||||
{
|
||||
adapter.DisableForegroundDispatch(this);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,15 +275,14 @@ namespace Bit.Android
|
||||
{
|
||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||
GeneratorTile = Intent.GetBooleanExtra("generatorTile", 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);
|
||||
@@ -269,8 +295,82 @@ namespace Bit.Android
|
||||
options.SaveCardExpYear = Intent.GetStringExtra("autofillFrameworkCardExpYear");
|
||||
options.SaveCardCode = Intent.GetStringExtra("autofillFrameworkCardCode");
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
private void ParseYubiKey(string data)
|
||||
{
|
||||
if(data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var otpMatch = _otpPattern.Matcher(data);
|
||||
if(otpMatch.Matches())
|
||||
{
|
||||
var otp = otpMatch.Group(1);
|
||||
_messagingService.Send("gotYubiKeyOTP", otp);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTheme(string theme)
|
||||
{
|
||||
if(theme == "dark")
|
||||
{
|
||||
SetTheme(Resource.Style.DarkTheme);
|
||||
}
|
||||
else if(theme == "black")
|
||||
{
|
||||
SetTheme(Resource.Style.BlackTheme);
|
||||
}
|
||||
else if(theme == "nord")
|
||||
{
|
||||
SetTheme(Resource.Style.NordTheme);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTheme(Resource.Style.LightTheme);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestartApp()
|
||||
{
|
||||
var intent = new Intent(this, typeof(MainActivity));
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 5923650, intent, PendingIntentFlags.CancelCurrent);
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + 500;
|
||||
alarmManager.Set(AlarmType.Rtc, triggerMs, pendingIntent);
|
||||
Java.Lang.JavaSystem.Exit(0);
|
||||
}
|
||||
|
||||
private void ExitApp()
|
||||
{
|
||||
FinishAffinity();
|
||||
Java.Lang.JavaSystem.Exit(0);
|
||||
}
|
||||
|
||||
private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
|
||||
{
|
||||
if(data.Item3)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var clearMs = data.Item2;
|
||||
if(clearMs == null)
|
||||
{
|
||||
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
|
||||
if(clearSeconds != null)
|
||||
{
|
||||
clearMs = clearSeconds.Value * 1000;
|
||||
}
|
||||
}
|
||||
if(clearMs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
await _stateService.SaveAsync(Constants.LastClipboardValueKey, data.Item1);
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
|
||||
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
|
||||
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,172 +1,158 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using Bit.Android.Services;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Repositories;
|
||||
using Bit.App.Services;
|
||||
using Plugin.Connectivity;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Services;
|
||||
using Bit.Droid.Utilities;
|
||||
using Plugin.CurrentActivity;
|
||||
using Plugin.Fingerprint;
|
||||
using Plugin.Settings;
|
||||
using XLabs.Ioc;
|
||||
using System.Threading.Tasks;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using FFImageLoading.Forms.Droid;
|
||||
using XLabs.Ioc.SimpleInjectorContainer;
|
||||
using SimpleInjector;
|
||||
using Xamarin.Android.Net;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
|
||||
namespace Bit.Android
|
||||
namespace Bit.Droid
|
||||
{
|
||||
#if DEBUG
|
||||
[Application(Debuggable = true)]
|
||||
#else
|
||||
[Application(Debuggable = false)]
|
||||
#endif
|
||||
public class MainApplication : Application, Application.IActivityLifecycleCallbacks
|
||||
[Register("com.x8bit.bitwarden.MainApplication")]
|
||||
#if FDROID
|
||||
public class MainApplication : Application
|
||||
#else
|
||||
public class MainApplication : Application, ProviderInstaller.IProviderInstallListener
|
||||
#endif
|
||||
{
|
||||
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)
|
||||
if(ServiceContainer.RegisteredServices.Count == 0)
|
||||
{
|
||||
SetIoc(this);
|
||||
RegisterLocalServices();
|
||||
ServiceContainer.Init(new AndroidClientHandler());
|
||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
||||
{
|
||||
var task = App.Migration.MigrationHelpers.PerformMigrationAsync();
|
||||
Task.Delay(2000).Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AndroidEnvironment_UnhandledExceptionRaiser(object sender, RaiseThrowableEventArgs e)
|
||||
{
|
||||
var message = Utilities.AppendExceptionToMessage("", e.Exception);
|
||||
//Utilities.SaveCrashFile(message, true);
|
||||
Utilities.SendCrashEmail(message, false);
|
||||
#if !FDROID
|
||||
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
{
|
||||
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
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;
|
||||
Bootstrap();
|
||||
CrossCurrentActivity.Current.Init(this);
|
||||
}
|
||||
|
||||
public override void OnTerminate()
|
||||
{
|
||||
base.OnTerminate();
|
||||
UnregisterActivityLifecycleCallbacks(this);
|
||||
}
|
||||
|
||||
public void OnActivityCreated(Activity activity, Bundle savedInstanceState)
|
||||
{
|
||||
CrossCurrentActivity.Current.Activity = activity;
|
||||
}
|
||||
|
||||
public void OnActivityDestroyed(Activity activity)
|
||||
public void OnProviderInstallFailed(int errorCode, Intent recoveryIntent)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnActivityPaused(Activity activity)
|
||||
public void OnProviderInstalled()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnActivityResumed(Activity activity)
|
||||
private void RegisterLocalServices()
|
||||
{
|
||||
CrossCurrentActivity.Current.Activity = activity;
|
||||
}
|
||||
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
|
||||
ServiceContainer.Register("settingsShim", new App.Migration.SettingsShim());
|
||||
if(App.Migration.MigrationHelpers.NeedsMigration())
|
||||
{
|
||||
ServiceContainer.Register<App.Migration.Abstractions.IOldSecureStorageService>(
|
||||
"oldSecureStorageService", new Migration.AndroidKeyStoreStorageService());
|
||||
}
|
||||
|
||||
public void OnActivitySaveInstanceState(Activity activity, Bundle outState)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnActivityStarted(Activity activity)
|
||||
{
|
||||
CrossCurrentActivity.Current.Activity = activity;
|
||||
}
|
||||
|
||||
public void OnActivityStopped(Activity activity)
|
||||
{
|
||||
}
|
||||
|
||||
public static void SetIoc(Application application)
|
||||
{
|
||||
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
|
||||
CachedImageRenderer.Init();
|
||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||
// Note: This might cause a race condition. Investigate more.
|
||||
Task.Run(() =>
|
||||
{
|
||||
FFImageLoading.Forms.Platform.CachedImageRenderer.Init(true);
|
||||
FFImageLoading.ImageService.Instance.Initialize(new FFImageLoading.Config.Configuration
|
||||
{
|
||||
FadeAnimationEnabled = false,
|
||||
FadeAnimationForCachedImages = false
|
||||
});
|
||||
ZXing.Net.Mobile.Forms.Android.Platform.Init();
|
||||
});
|
||||
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
|
||||
CrossFingerprint.SetDialogFragmentType<CustomFingerprintDialogFragment>();
|
||||
|
||||
//var container = new UnityContainer();
|
||||
var container = new Container();
|
||||
var preferencesStorage = new PreferencesStorageService(null);
|
||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||
liteDbStorage.InitAsync();
|
||||
var localizeService = new LocalizeService();
|
||||
var broadcasterService = new BroadcasterService();
|
||||
var messagingService = new MobileBroadcasterMessagingService(broadcasterService);
|
||||
var i18nService = new MobileI18nService(localizeService.GetCurrentCultureInfo());
|
||||
var secureStorageService = new SecureStorageService();
|
||||
var cryptoPrimitiveService = new CryptoPrimitiveService();
|
||||
var mobileStorageService = new MobileStorageService(preferencesStorage, liteDbStorage);
|
||||
var deviceActionService = new DeviceActionService(mobileStorageService, messagingService,
|
||||
broadcasterService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
|
||||
// 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<IReflectionService, ReflectionService>();
|
||||
container.RegisterSingleton<ILockService, LockService>();
|
||||
container.RegisterSingleton<IAppInfoService, AppInfoService>();
|
||||
container.RegisterSingleton<IGoogleAnalyticsService, GoogleAnalyticsService>();
|
||||
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<IMemoryService, MemoryService>();
|
||||
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);
|
||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
|
||||
ServiceContainer.Register<II18nService>("i18nService", i18nService);
|
||||
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
|
||||
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
|
||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
|
||||
// Push
|
||||
container.RegisterSingleton<IPushNotificationListener, PushNotificationListener>();
|
||||
container.RegisterSingleton<IPushNotificationService, AndroidPushNotificationService>();
|
||||
#if FDROID
|
||||
ServiceContainer.Register<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService", new NoopPushNotificationListenerService());
|
||||
ServiceContainer.Register<IPushNotificationService>(
|
||||
"pushNotificationService", new NoopPushNotificationService());
|
||||
#else
|
||||
var notificationListenerService = new PushNotificationListenerService();
|
||||
ServiceContainer.Register<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService", notificationListenerService);
|
||||
var androidPushNotificationService = new AndroidPushNotificationService(
|
||||
mobileStorageService, notificationListenerService);
|
||||
ServiceContainer.Register<IPushNotificationService>(
|
||||
"pushNotificationService", androidPushNotificationService);
|
||||
#endif
|
||||
}
|
||||
|
||||
container.Verify();
|
||||
Resolver.SetResolver(new SimpleInjectorResolver(container));
|
||||
private void Bootstrap()
|
||||
{
|
||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||
// Note: This is not awaited
|
||||
var bootstrapTask = BootstrapAsync();
|
||||
}
|
||||
|
||||
private async Task BootstrapAsync()
|
||||
{
|
||||
var disableFavicon = await ServiceContainer.Resolve<IStorageService>("storageService").GetAsync<bool?>(
|
||||
Constants.DisableFaviconKey);
|
||||
await ServiceContainer.Resolve<IStateService>("stateService").SaveAsync(Constants.DisableFaviconKey,
|
||||
disableFavicon);
|
||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,14 +8,16 @@ using Javax.Security.Auth.X500;
|
||||
using Java.Math;
|
||||
using Android.Security.Keystore;
|
||||
using Android.App;
|
||||
using Plugin.Settings.Abstractions;
|
||||
using Java.Util;
|
||||
using Javax.Crypto.Spec;
|
||||
using Android.Preferences;
|
||||
using Bit.App.Migration;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.App.Migration.Abstractions;
|
||||
|
||||
namespace Bit.Android.Services
|
||||
namespace Bit.Droid.Migration
|
||||
{
|
||||
public class AndroidKeyStoreStorageService : ISecureStorageService
|
||||
public class AndroidKeyStoreStorageService : IOldSecureStorageService
|
||||
{
|
||||
private const string AndroidKeyStore = "AndroidKeyStore";
|
||||
private const string AesMode = "AES/GCM/NoPadding";
|
||||
@@ -31,21 +33,31 @@ namespace Bit.Android.Services
|
||||
|
||||
private readonly string _rsaMode;
|
||||
private readonly bool _oldAndroid;
|
||||
private readonly ISettings _settings;
|
||||
private readonly SettingsShim _settings;
|
||||
private readonly KeyStore _keyStore;
|
||||
|
||||
public AndroidKeyStoreStorageService(ISettings settings)
|
||||
public AndroidKeyStoreStorageService()
|
||||
{
|
||||
_oldAndroid = Build.VERSION.SdkInt < BuildVersionCodes.M;
|
||||
_rsaMode = _oldAndroid ? "RSA/ECB/PKCS1Padding" : "RSA/ECB/OAEPWithSHA-1AndMGF1Padding";
|
||||
|
||||
_settings = settings;
|
||||
_settings = ServiceContainer.Resolve<SettingsShim>("settingsShim");
|
||||
|
||||
_keyStore = KeyStore.GetInstance(AndroidKeyStore);
|
||||
_keyStore.Load(null);
|
||||
|
||||
GenerateStoreKey();
|
||||
/*
|
||||
try
|
||||
{
|
||||
GenerateStoreKey(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
GenerateStoreKey(false);
|
||||
}
|
||||
|
||||
GenerateAesKey();
|
||||
*/
|
||||
}
|
||||
|
||||
public bool Contains(string key)
|
||||
@@ -87,7 +99,7 @@ namespace Bit.Android.Services
|
||||
|
||||
try
|
||||
{
|
||||
return App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKey);
|
||||
return App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKey);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -117,7 +129,7 @@ namespace Bit.Android.Services
|
||||
|
||||
try
|
||||
{
|
||||
var cipherString = App.Utilities.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||
var cipherString = App.Migration.Crypto.AesCbcEncrypt(dataBytes, aesKey);
|
||||
_settings.AddOrUpdateValue(formattedKey, cipherString.EncryptedString);
|
||||
}
|
||||
catch
|
||||
@@ -128,7 +140,7 @@ namespace Bit.Android.Services
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateStoreKey()
|
||||
private void GenerateStoreKey(bool withDate)
|
||||
{
|
||||
if(_keyStore.ContainsAlias(KeyAlias))
|
||||
{
|
||||
@@ -144,27 +156,33 @@ namespace Bit.Android.Services
|
||||
{
|
||||
var subject = new X500Principal($"CN={KeyAlias}");
|
||||
|
||||
var spec = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||
var builder = new KeyPairGeneratorSpec.Builder(Application.Context)
|
||||
.SetAlias(KeyAlias)
|
||||
.SetSubject(subject)
|
||||
.SetSerialNumber(BigInteger.Ten)
|
||||
.SetStartDate(new Date(0))
|
||||
.SetEndDate(end.Time)
|
||||
.Build();
|
||||
.SetSerialNumber(BigInteger.Ten);
|
||||
|
||||
if(withDate)
|
||||
{
|
||||
builder.SetStartDate(new Date(0)).SetEndDate(end.Time);
|
||||
}
|
||||
|
||||
var spec = builder.Build();
|
||||
var gen = KeyPairGenerator.GetInstance(KeyProperties.KeyAlgorithmRsa, AndroidKeyStore);
|
||||
gen.Initialize(spec);
|
||||
gen.GenerateKeyPair();
|
||||
}
|
||||
else
|
||||
{
|
||||
var spec = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||
var builder = new KeyGenParameterSpec.Builder(KeyAlias, KeyStorePurpose.Decrypt | KeyStorePurpose.Encrypt)
|
||||
.SetBlockModes(KeyProperties.BlockModeGcm)
|
||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone)
|
||||
.SetKeyValidityStart(new Date(0))
|
||||
.SetKeyValidityEnd(end.Time)
|
||||
.Build();
|
||||
.SetEncryptionPaddings(KeyProperties.EncryptionPaddingNone);
|
||||
|
||||
if(withDate)
|
||||
{
|
||||
builder.SetKeyValidityStart(new Date(0)).SetKeyValidityEnd(end.Time);
|
||||
}
|
||||
|
||||
var spec = builder.Build();
|
||||
var gen = KeyGenerator.GetInstance(KeyProperties.KeyAlgorithmAes, AndroidKeyStore);
|
||||
gen.Init(spec);
|
||||
gen.GenerateKey();
|
||||
@@ -183,12 +201,12 @@ namespace Bit.Android.Services
|
||||
return;
|
||||
}
|
||||
|
||||
var key = App.Utilities.Crypto.RandomBytes(512 / 8);
|
||||
var key = App.Migration.Crypto.RandomBytes(512 / 8);
|
||||
var encKey = _oldAndroid ? RsaEncrypt(key) : AesEncrypt(key);
|
||||
_settings.AddOrUpdateValue(AesKey, encKey);
|
||||
}
|
||||
|
||||
private App.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
|
||||
private App.Migration.Models.SymmetricCryptoKey GetAesKey(bool v1 = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -208,7 +226,7 @@ namespace Bit.Android.Services
|
||||
{
|
||||
var encKeyBytes = Convert.FromBase64String(encKey);
|
||||
var key = RsaDecrypt(encKeyBytes, v1);
|
||||
return new App.Models.SymmetricCryptoKey(key);
|
||||
return new App.Migration.Models.SymmetricCryptoKey(key);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -221,7 +239,7 @@ namespace Bit.Android.Services
|
||||
var ivBytes = Convert.FromBase64String(parts[0]);
|
||||
var encKeyBytes = Convert.FromBase64String(parts[1]);
|
||||
var key = AesDecrypt(ivBytes, encKeyBytes);
|
||||
return new App.Models.SymmetricCryptoKey(key);
|
||||
return new App.Migration.Models.SymmetricCryptoKey(key);
|
||||
}
|
||||
}
|
||||
catch
|
||||
@@ -303,7 +321,7 @@ namespace Bit.Android.Services
|
||||
try
|
||||
{
|
||||
var cs = _settings.GetValueOrDefault(formattedKeyV1, null);
|
||||
var value = App.Utilities.Crypto.AesCbcDecrypt(new App.Models.CipherString(cs), aesKeyV1);
|
||||
var value = App.Migration.Crypto.AesCbcDecrypt(new App.Migration.Models.CipherString(cs), aesKeyV1);
|
||||
Store(key, value);
|
||||
return value;
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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,39 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.14.1" android:installLocation="auto" android:versionCode="502" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="1"
|
||||
android:versionName="2.0.6"
|
||||
package="com.x8bit.bitwarden">
|
||||
|
||||
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="28" />
|
||||
|
||||
<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="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<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">
|
||||
<application
|
||||
android:label="Bitwarden"
|
||||
android:theme="@style/LightTheme.Splash"
|
||||
android:allowBackup="false"
|
||||
tools:replace="android:allowBackup"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.x8bit.bitwarden.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
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" />
|
||||
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">
|
||||
<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}" />
|
||||
<category android:name="com.x8bit.bitwarden" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<activity android:name="net.hockeyapp.android.UpdateActivity" android:exported="false" android:icon="@drawable/icon" />
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Android.App;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
@@ -10,8 +8,8 @@ using Android.App;
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("8bit Solutions LLC")]
|
||||
[assembly: AssemblyProduct("bitwarden")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyProduct("Bitwarden")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2016")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
25
src/Android/Push/FirebaseInstanceIdService.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
#if !FDROID
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Firebase.Iid;
|
||||
|
||||
namespace Bit.Droid.Push
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.INSTANCE_ID_EVENT" })]
|
||||
public class FirebaseInstanceIdService : Firebase.Iid.FirebaseInstanceIdService
|
||||
{
|
||||
public async override void OnTokenRefresh()
|
||||
{
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
await storageService.SaveAsync(Constants.PushRegisteredTokenKey, FirebaseInstanceId.Instance.Token);
|
||||
await pushNotificationService.RegisterAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -1,37 +1,36 @@
|
||||
using System;
|
||||
#if !FDROID
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Firebase.Messaging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xamarin.Forms;
|
||||
using XLabs.Ioc;
|
||||
|
||||
namespace Bit.Android
|
||||
namespace Bit.Droid.Push
|
||||
{
|
||||
[Service]
|
||||
[IntentFilter(new[] { "com.google.firebase.MESSAGING_EVENT" })]
|
||||
public class FirebaseMessagingService : Firebase.Messaging.FirebaseMessagingService
|
||||
{
|
||||
public override void OnMessageReceived(RemoteMessage message)
|
||||
public async 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);
|
||||
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService");
|
||||
await listener.OnMessageAsync(obj, Device.Android);
|
||||
}
|
||||
catch(JsonReaderException ex)
|
||||
{
|
||||
@@ -40,3 +39,4 @@ namespace Bit.Android
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
23
src/Android/Receivers/ClearClipboardAlarmReceiver.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Android.Content;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.ClearClipboardAlarmReceiver", Exported = false)]
|
||||
public class ClearClipboardAlarmReceiver : BroadcastReceiver
|
||||
{
|
||||
public async override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||
var lastClipboardValue = await stateService.GetAsync<string>(Constants.LastClipboardValueKey);
|
||||
await stateService.RemoveAsync(Constants.LastClipboardValueKey);
|
||||
if(lastClipboardValue == clipboardManager.Text)
|
||||
{
|
||||
clipboardManager.Text = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
src/Android/Receivers/LockAlarmReceiver.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using Android.Content;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.LockAlarmReceiver", Exported = false)]
|
||||
public class LockAlarmReceiver : BroadcastReceiver
|
||||
{
|
||||
public async override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
|
||||
await lockService.CheckLockAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Android/Receivers/PackageReplacedReceiver.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
[BroadcastReceiver(Name = "com.x8bit.bitwarden.PackageReplacedReceiver", Exported = false)]
|
||||
[IntentFilter(new[] { Intent.ActionMyPackageReplaced })]
|
||||
public class PackageReplacedReceiver : BroadcastReceiver
|
||||
{
|
||||
public override async void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var storageService = ServiceContainer.Resolve<IStorageService>("storageService");
|
||||
await AppHelpers.PerformUpdateTasksAsync(ServiceContainer.Resolve<ISyncService>("syncService"),
|
||||
ServiceContainer.Resolve<IDeviceActionService>("deviceActionService"), storageService);
|
||||
}
|
||||
}
|
||||
}
|
||||
241
src/Android/Renderers/CipherViewCellRenderer.cs
Normal file
@@ -0,0 +1,241 @@
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Graphics;
|
||||
using Android.Runtime;
|
||||
using Android.Util;
|
||||
using Android.Views;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Widget;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Droid.Renderers;
|
||||
using FFImageLoading;
|
||||
using FFImageLoading.Views;
|
||||
using FFImageLoading.Work;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(CipherViewCell), typeof(CipherViewCellRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CipherViewCellRenderer : ViewCellRenderer
|
||||
{
|
||||
private static Typeface _faTypeface;
|
||||
private static Typeface _miTypeface;
|
||||
private static Android.Graphics.Color _textColor;
|
||||
private static Android.Graphics.Color _mutedColor;
|
||||
private static Android.Graphics.Color _disabledIconColor;
|
||||
|
||||
private AndroidCipherCell _cell;
|
||||
|
||||
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,
|
||||
ViewGroup parent, Context context)
|
||||
{
|
||||
if(_faTypeface == null)
|
||||
{
|
||||
_faTypeface = Typeface.CreateFromAsset(context.Assets, "FontAwesome.ttf");
|
||||
}
|
||||
if(_miTypeface == null)
|
||||
{
|
||||
_miTypeface = Typeface.CreateFromAsset(context.Assets, "MaterialIcons_Regular.ttf");
|
||||
}
|
||||
if(_textColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_textColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["TextColor"])
|
||||
.ToAndroid();
|
||||
}
|
||||
if(_mutedColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_mutedColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MutedColor"])
|
||||
.ToAndroid();
|
||||
}
|
||||
if(_disabledIconColor == default(Android.Graphics.Color))
|
||||
{
|
||||
_disabledIconColor =
|
||||
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
|
||||
.ToAndroid();
|
||||
}
|
||||
|
||||
var cipherCell = item as CipherViewCell;
|
||||
_cell = convertView as AndroidCipherCell;
|
||||
if(_cell == null)
|
||||
{
|
||||
_cell = new AndroidCipherCell(context, cipherCell, _faTypeface, _miTypeface);
|
||||
}
|
||||
else
|
||||
{
|
||||
_cell.CipherViewCell.PropertyChanged -= CellPropertyChanged;
|
||||
}
|
||||
cipherCell.PropertyChanged += CellPropertyChanged;
|
||||
_cell.UpdateCell(cipherCell);
|
||||
_cell.UpdateColors(_textColor, _mutedColor, _disabledIconColor);
|
||||
return _cell;
|
||||
}
|
||||
|
||||
public void CellPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var cipherCell = sender as CipherViewCell;
|
||||
_cell.CipherViewCell = cipherCell;
|
||||
if(e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
|
||||
{
|
||||
_cell.UpdateCell(cipherCell);
|
||||
}
|
||||
else if(e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
|
||||
{
|
||||
_cell.UpdateIconImage(cipherCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AndroidCipherCell : LinearLayout, INativeElementView
|
||||
{
|
||||
private readonly Typeface _faTypeface;
|
||||
private readonly Typeface _miTypeface;
|
||||
|
||||
private IScheduledWork _currentTask;
|
||||
|
||||
public AndroidCipherCell(Context context, CipherViewCell cipherView, Typeface faTypeface, Typeface miTypeface)
|
||||
: base(context)
|
||||
{
|
||||
CipherViewCell = cipherView;
|
||||
_faTypeface = faTypeface;
|
||||
_miTypeface = miTypeface;
|
||||
|
||||
var view = (context as Activity).LayoutInflater.Inflate(Resource.Layout.CipherViewCell, null);
|
||||
IconImage = view.FindViewById<IconImageView>(Resource.Id.CipherCellIconImage);
|
||||
Icon = view.FindViewById<TextView>(Resource.Id.CipherCellIcon);
|
||||
Name = view.FindViewById<TextView>(Resource.Id.CipherCellName);
|
||||
SubTitle = view.FindViewById<TextView>(Resource.Id.CipherCellSubTitle);
|
||||
SharedIcon = view.FindViewById<TextView>(Resource.Id.CipherCellSharedIcon);
|
||||
AttachmentsIcon = view.FindViewById<TextView>(Resource.Id.CipherCellAttachmentsIcon);
|
||||
MoreButton = view.FindViewById<Android.Widget.Button>(Resource.Id.CipherCellButton);
|
||||
MoreButton.Click += MoreButton_Click;
|
||||
|
||||
Icon.Typeface = _faTypeface;
|
||||
SharedIcon.Typeface = _faTypeface;
|
||||
AttachmentsIcon.Typeface = _faTypeface;
|
||||
MoreButton.Typeface = _miTypeface;
|
||||
|
||||
var small = (float)Device.GetNamedSize(NamedSize.Small, typeof(Label));
|
||||
Icon.SetTextSize(ComplexUnitType.Pt, 10);
|
||||
Name.SetTextSize(ComplexUnitType.Sp, (float)Device.GetNamedSize(NamedSize.Medium, typeof(Label)));
|
||||
SubTitle.SetTextSize(ComplexUnitType.Sp, small);
|
||||
SharedIcon.SetTextSize(ComplexUnitType.Sp, small);
|
||||
AttachmentsIcon.SetTextSize(ComplexUnitType.Sp, small);
|
||||
MoreButton.SetTextSize(ComplexUnitType.Sp, 25);
|
||||
|
||||
AddView(view);
|
||||
}
|
||||
|
||||
public CipherViewCell CipherViewCell { get; set; }
|
||||
public Element Element => CipherViewCell;
|
||||
|
||||
public IconImageView IconImage { get; set; }
|
||||
public TextView Icon { get; set; }
|
||||
public TextView Name { get; set; }
|
||||
public TextView SubTitle { get; set; }
|
||||
public TextView SharedIcon { get; set; }
|
||||
public TextView AttachmentsIcon { get; set; }
|
||||
public Android.Widget.Button MoreButton { get; set; }
|
||||
|
||||
public void UpdateCell(CipherViewCell cipherCell)
|
||||
{
|
||||
UpdateIconImage(cipherCell);
|
||||
|
||||
var cipher = cipherCell.Cipher;
|
||||
Name.Text = cipher.Name;
|
||||
if(!string.IsNullOrWhiteSpace(cipher.SubTitle))
|
||||
{
|
||||
SubTitle.Text = cipher.SubTitle;
|
||||
SubTitle.Visibility = ViewStates.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
SubTitle.Visibility = ViewStates.Invisible;
|
||||
}
|
||||
SharedIcon.Visibility = cipher.Shared ? ViewStates.Visible : ViewStates.Gone;
|
||||
AttachmentsIcon.Visibility = cipher.HasAttachments ? ViewStates.Visible : ViewStates.Gone;
|
||||
}
|
||||
|
||||
public void UpdateIconImage(CipherViewCell cipherCell)
|
||||
{
|
||||
if(_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
|
||||
{
|
||||
_currentTask.Cancel();
|
||||
}
|
||||
|
||||
var cipher = cipherCell.Cipher;
|
||||
|
||||
var iconImage = cipherCell.GetIconImage(cipher);
|
||||
if(iconImage.Item2 != null)
|
||||
{
|
||||
IconImage.SetImageResource(Resource.Drawable.login);
|
||||
IconImage.Visibility = ViewStates.Visible;
|
||||
Icon.Visibility = ViewStates.Gone;
|
||||
_currentTask = ImageService.Instance.LoadUrl(iconImage.Item2).DownSample(64).Into(IconImage);
|
||||
IconImage.Key = iconImage.Item2;
|
||||
}
|
||||
else
|
||||
{
|
||||
IconImage.Visibility = ViewStates.Gone;
|
||||
Icon.Visibility = ViewStates.Visible;
|
||||
Icon.Text = iconImage.Item1;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateColors(Android.Graphics.Color textColor, Android.Graphics.Color mutedColor,
|
||||
Android.Graphics.Color iconDisabledColor)
|
||||
{
|
||||
Name.SetTextColor(textColor);
|
||||
SubTitle.SetTextColor(mutedColor);
|
||||
Icon.SetTextColor(mutedColor);
|
||||
SharedIcon.SetTextColor(mutedColor);
|
||||
AttachmentsIcon.SetTextColor(mutedColor);
|
||||
MoreButton.SetTextColor(iconDisabledColor);
|
||||
}
|
||||
|
||||
private void MoreButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
if(CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
|
||||
{
|
||||
CipherViewCell.ButtonCommand.Execute(CipherViewCell.Cipher);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if(disposing)
|
||||
{
|
||||
MoreButton.Click -= MoreButton_Click;
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
[Android.Runtime.Preserve(AllMembers = true)]
|
||||
[Register("bit.droid.renderers.IconImageView")]
|
||||
public class IconImageView : ImageViewAsync
|
||||
{
|
||||
public IconImageView(Context context) : base(context)
|
||||
{ }
|
||||
|
||||
public IconImageView(IntPtr javaReference, JniHandleOwnership transfer)
|
||||
: base(javaReference, transfer)
|
||||
{ }
|
||||
|
||||
public IconImageView(Context context, IAttributeSet attrs)
|
||||
: base(context, attrs)
|
||||
{ }
|
||||
|
||||
public string Key { get; set; }
|
||||
|
||||
protected override void JavaFinalize()
|
||||
{
|
||||
SetImageDrawable(null);
|
||||
SetImageBitmap(null);
|
||||
ImageService.Instance.InvalidateCacheEntryAsync(Key, FFImageLoading.Cache.CacheType.Memory);
|
||||
base.JavaFinalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Android/Renderers/CustomEditorRenderer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Android.Content;
|
||||
using Android.Views.InputMethods;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Editor), typeof(CustomEditorRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomEditorRenderer : EditorRenderer
|
||||
{
|
||||
public CustomEditorRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
||||
(ImeAction)ImeFlags.NoExtractUi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
src/Android/Renderers/CustomEntryRenderer.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using Android.Content;
|
||||
using Android.Views.InputMethods;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Entry), typeof(CustomEntryRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomEntryRenderer : EntryRenderer
|
||||
{
|
||||
public CustomEntryRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
Control.ImeOptions = Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
||||
(ImeAction)ImeFlags.NoExtractUi;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/Android/Renderers/CustomPickerRenderer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using Android.Content;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(Picker), typeof(CustomPickerRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomPickerRenderer : PickerRenderer
|
||||
{
|
||||
public CustomPickerRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
{
|
||||
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
|
||||
Control.PaddingBottom + 20);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/Android/Renderers/CustomSearchBarRenderer.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Android.Content;
|
||||
using Android.Views.InputMethods;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(SearchBar), typeof(CustomSearchBarRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomSearchBarRenderer : SearchBarRenderer
|
||||
{
|
||||
public CustomSearchBarRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && e.NewElement != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var magId = Resources.GetIdentifier("android:id/search_mag_icon", null, null);
|
||||
var magImage = (Android.Widget.ImageView)Control.FindViewById(magId);
|
||||
magImage.LayoutParameters = new Android.Widget.LinearLayout.LayoutParams(0, 0);
|
||||
}
|
||||
catch { }
|
||||
Control.SetImeOptions(Control.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning |
|
||||
(ImeAction)ImeFlags.NoExtractUi);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
using Android.Content;
|
||||
using Android.Views;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedListView), typeof(ExtendedListViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class ExtendedListViewRenderer : ListViewRenderer
|
||||
{
|
||||
@@ -18,15 +17,12 @@ namespace Bit.Android.Controls
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if(e.NewElement is ExtendedListView listView)
|
||||
if(Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
|
||||
{
|
||||
if(listView.BottomPadding > 0)
|
||||
{
|
||||
Control.SetPadding(0, 0, 0, listView.BottomPadding);
|
||||
Control.SetClipToPadding(false);
|
||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
||||
}
|
||||
// Pad for FAB
|
||||
Control.SetPadding(0, 0, 0, 170);
|
||||
Control.SetClipToPadding(false);
|
||||
Control.ScrollBarStyle = ScrollbarStyles.OutsideOverlay;
|
||||
}
|
||||
}
|
||||
}
|
||||
33
src/Android/Renderers/ExtendedSliderRenderer.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Android.Content;
|
||||
using Android.Graphics.Drawables;
|
||||
using Android.Support.V4.Content.Res;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ExtendedSlider), typeof(ExtendedSliderRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class ExtendedSliderRenderer : SliderRenderer
|
||||
{
|
||||
public ExtendedSliderRenderer(Context context)
|
||||
: base(context)
|
||||
{}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
if(Control != null && Element is ExtendedSlider view)
|
||||
{
|
||||
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
|
||||
if(t is GradientDrawable thumb)
|
||||
{
|
||||
thumb.SetColor(view.ThumbColor.ToAndroid());
|
||||
thumb.SetStroke(3, view.ThumbBorderColor.ToAndroid());
|
||||
Control.SetThumb(thumb);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Bit.Android.Controls;
|
||||
using Bit.App.Controls;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
@@ -7,18 +6,23 @@ using Android.Webkit;
|
||||
using AWebkit = Android.Webkit;
|
||||
using Java.Interop;
|
||||
using Android.Content;
|
||||
using Plugin.CurrentActivity;
|
||||
using Bit.Droid.Renderers;
|
||||
using System.ComponentModel;
|
||||
|
||||
[assembly: ExportRenderer(typeof(HybridWebView), typeof(HybridWebViewRenderer))]
|
||||
namespace Bit.Android.Controls
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class HybridWebViewRenderer : ViewRenderer<HybridWebView, AWebkit.WebView>
|
||||
{
|
||||
private const string JSFunction = "function invokeCSharpAction(data){jsBridge.invokeAction(data);}";
|
||||
|
||||
private readonly Context _context;
|
||||
|
||||
public HybridWebViewRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
|
||||
{
|
||||
@@ -26,31 +30,30 @@ namespace Bit.Android.Controls
|
||||
|
||||
if(Control == null)
|
||||
{
|
||||
var webView = new AWebkit.WebView(CrossCurrentActivity.Current.Activity);
|
||||
var webView = new AWebkit.WebView(_context);
|
||||
webView.Settings.JavaScriptEnabled = true;
|
||||
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
|
||||
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)
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
if(Control != null)
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
if(e.PropertyName == HybridWebView.UriProperty.PropertyName)
|
||||
{
|
||||
Control.LoadUrl(string.Format("javascript: {0}", script));
|
||||
Control.LoadUrl(Element.Uri);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +70,29 @@ namespace Bit.Android.Controls
|
||||
[Export("invokeAction")]
|
||||
public void InvokeAction(string data)
|
||||
{
|
||||
HybridWebViewRenderer hybridRenderer;
|
||||
if(_hybridWebViewRenderer != null && _hybridWebViewRenderer.TryGetTarget(out hybridRenderer))
|
||||
if(_hybridWebViewRenderer != null &&
|
||||
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
|
||||
{
|
||||
hybridRenderer.Element.InvokeAction(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class JSWebViewClient : WebViewClient
|
||||
{
|
||||
private readonly string _javascript;
|
||||
|
||||
public JSWebViewClient(string javascript)
|
||||
{
|
||||
_javascript = javascript;
|
||||
|
||||
}
|
||||
|
||||
public override void OnPageFinished(AWebkit.WebView view, string url)
|
||||
{
|
||||
base.OnPageFinished(view, url);
|
||||
view.EvaluateJavascript(_javascript, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
Images, layout descriptions, binary blobs and string dictionaries can be included
|
||||
in your application as resource files. Various Android APIs are designed to
|
||||
operate on the resource IDs instead of dealing with images, strings or binary blobs
|
||||
directly.
|
||||
|
||||
For example, a sample Android app that contains a user interface layout (main.xml),
|
||||
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
|
||||
would keep its resources in the "Resources" directory of the application:
|
||||
|
||||
Resources/
|
||||
drawable-hdpi/
|
||||
icon.png
|
||||
|
||||
drawable-ldpi/
|
||||
icon.png
|
||||
|
||||
drawable-mdpi/
|
||||
icon.png
|
||||
|
||||
layout/
|
||||
main.xml
|
||||
|
||||
values/
|
||||
strings.xml
|
||||
|
||||
In order to get the build system to recognize Android resources, set the build action to
|
||||
"AndroidResource". The native Android APIs do not operate directly with filenames, but
|
||||
instead operate on resource IDs. When you compile an Android application that uses resources,
|
||||
the build system will package the resources for distribution and generate a class called
|
||||
"Resource" that contains the tokens for each one of the resources included. For example,
|
||||
for the above Resources layout, this is what the Resource class would expose:
|
||||
|
||||
public class Resource {
|
||||
public class drawable {
|
||||
public const int icon = 0x123;
|
||||
}
|
||||
|
||||
public class layout {
|
||||
public const int main = 0x456;
|
||||
}
|
||||
|
||||
public class strings {
|
||||
public const int first_string = 0xabc;
|
||||
public const int second_string = 0xbcd;
|
||||
}
|
||||
}
|
||||
|
||||
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
|
||||
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
|
||||
string in the dictionary file values/strings.xml.
|
||||
10287
src/Android/Resources/Resource.Designer.cs
generated
15621
src/Android/Resources/Resource.designer.cs
generated
Normal file
|
Before Width: | Height: | Size: 530 B |
|
Before Width: | Height: | Size: 625 B |
|
Before Width: | Height: | Size: 664 B |
|
Before Width: | Height: | Size: 426 B |
|
Before Width: | Height: | Size: 977 B |
|
Before Width: | Height: | Size: 883 B After Width: | Height: | Size: 775 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 738 B |
|
Before Width: | Height: | Size: 467 B |
|
Before Width: | Height: | Size: 518 B |
|
Before Width: | Height: | Size: 833 B |
|
Before Width: | Height: | Size: 937 B |
|
Before Width: | Height: | Size: 569 B |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 431 B |
|
Before Width: | Height: | Size: 482 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 513 B |
|
Before Width: | Height: | Size: 686 B |
|
Before Width: | Height: | Size: 634 B |
|
Before Width: | Height: | Size: 438 B After Width: | Height: | Size: 581 B |
BIN
src/Android/Resources/drawable-hdpi/logo_white.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 218 B |
|
Before Width: | Height: | Size: 344 B |
|
Before Width: | Height: | Size: 531 B |
|
Before Width: | Height: | Size: 658 B |