Compare commits
500 Commits
v2022.05.0
...
vault/pm-5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
621bf76db8 | ||
|
|
f14be2a3a2 | ||
|
|
8ee744b746 | ||
|
|
15a03ba573 | ||
|
|
82711a0235 | ||
|
|
e6635564aa | ||
|
|
6c078fe343 | ||
|
|
743e71ff92 | ||
|
|
7b579b7aa5 | ||
|
|
fe10fd7766 | ||
|
|
3c0de8aacc | ||
|
|
18d9a77f25 | ||
|
|
9eca82a62b | ||
|
|
b90e030b8f | ||
|
|
9a28419a4e | ||
|
|
f4c468e6a1 | ||
|
|
2c346eb710 | ||
|
|
9c0908f7b7 | ||
|
|
827fbbc9ce | ||
|
|
5b249bed67 | ||
|
|
afbcb212f6 | ||
|
|
a71c28536d | ||
|
|
ba5fa8a518 | ||
|
|
65ea5574de | ||
|
|
f013f69669 | ||
|
|
f98dfa6581 | ||
|
|
0723999652 | ||
|
|
96343eccf7 | ||
|
|
793c5fef6f | ||
|
|
3a13ba4efa | ||
|
|
c5288d3921 | ||
|
|
9506595fdd | ||
|
|
7a65bf7fd7 | ||
|
|
d0ce89fedb | ||
|
|
3c94ea4579 | ||
|
|
658c1eaf64 | ||
|
|
02b0265767 | ||
|
|
bd2481b3e4 | ||
|
|
12c72b2833 | ||
|
|
2e5fb414b5 | ||
|
|
4dda7a6634 | ||
|
|
a1808f64b3 | ||
|
|
142c3145f0 | ||
|
|
72de17bd1d | ||
|
|
ed3467515e | ||
|
|
21fc56457d | ||
|
|
bc2eb212a6 | ||
|
|
a1912526c2 | ||
|
|
9d0209751c | ||
|
|
f2936c95fa | ||
|
|
bb2f1f0f5f | ||
|
|
5a0c2115a1 | ||
|
|
a67f50b145 | ||
|
|
757e5ea647 | ||
|
|
b23f29511c | ||
|
|
71731bb9b7 | ||
|
|
f2be840a7d | ||
|
|
685e0f407a | ||
|
|
bbef0f8c93 | ||
|
|
3cdf5ccd3b | ||
|
|
e97a37222a | ||
|
|
218a30b510 | ||
|
|
828043ec97 | ||
|
|
b25c8b0842 | ||
|
|
a4a0d31fc6 | ||
|
|
6ef6cf5d84 | ||
|
|
597f629920 | ||
|
|
b8cef16711 | ||
|
|
c4f6ae9077 | ||
|
|
8b9658d2c5 | ||
|
|
43bf0fbdb3 | ||
|
|
11922c6f49 | ||
|
|
a6f05338c2 | ||
|
|
b932824b5a | ||
|
|
efd1671f48 | ||
|
|
3e2005e5ed | ||
|
|
382eee2ed3 | ||
|
|
b0f1dd00ee | ||
|
|
5961a001ab | ||
|
|
9026dd10e5 | ||
|
|
355261679d | ||
|
|
7f14ec9b5d | ||
|
|
0c72626916 | ||
|
|
f21fae7fea | ||
|
|
6d4792bc24 | ||
|
|
dbadf8c56f | ||
|
|
4d0f9d1c03 | ||
|
|
68759fc608 | ||
|
|
47be3d6aef | ||
|
|
7ec5c8ccfd | ||
|
|
819aabb330 | ||
|
|
9c7ff853d7 | ||
|
|
e30f9903d1 | ||
|
|
249406e3a8 | ||
|
|
8cae840c68 | ||
|
|
e274c04107 | ||
|
|
7043be67dd | ||
|
|
afb8c515d6 | ||
|
|
bfcfd367dd | ||
|
|
a23454bc53 | ||
|
|
6f7100ae4f | ||
|
|
01ac20e6e4 | ||
|
|
8474f536ff | ||
|
|
f426c0e370 | ||
|
|
420dc09fd1 | ||
|
|
6d4793d592 | ||
|
|
eea7c6b7d7 | ||
|
|
ec93a61275 | ||
|
|
c34d1da6e6 | ||
|
|
c4e64e082b | ||
|
|
5aaff1ea20 | ||
|
|
0271a4db4c | ||
|
|
375718f945 | ||
|
|
9eda015371 | ||
|
|
ea81acb3bf | ||
|
|
174549e5bc | ||
|
|
87b1d18872 | ||
|
|
ae9ba810ff | ||
|
|
dd52ff0dcc | ||
|
|
c678c17ebc | ||
|
|
cd9e49b13b | ||
|
|
6d7970f767 | ||
|
|
9adc4d3080 | ||
|
|
1f20f70d13 | ||
|
|
a25da68437 | ||
|
|
fdc0313d10 | ||
|
|
f31c87b52e | ||
|
|
1e79e1182f | ||
|
|
11947ce99a | ||
|
|
4abb472998 | ||
|
|
1d541e5b8e | ||
|
|
175b9936b6 | ||
|
|
72e67bd6f2 | ||
|
|
216c6abcf6 | ||
|
|
1014563c75 | ||
|
|
3506269811 | ||
|
|
31487a31bb | ||
|
|
1407aa5655 | ||
|
|
16f59e2698 | ||
|
|
d876b54f45 | ||
|
|
6644e3b449 | ||
|
|
8d98d1d5bd | ||
|
|
3e9711f8f2 | ||
|
|
3af37f01d3 | ||
|
|
43d2d386b1 | ||
|
|
bc5c11b47f | ||
|
|
52843b4181 | ||
|
|
98705e443f | ||
|
|
1332ef7b43 | ||
|
|
04e30c2146 | ||
|
|
f604da13a1 | ||
|
|
dcf9acb51c | ||
|
|
3b087c50ae | ||
|
|
1c13ed9895 | ||
|
|
eeb634e698 | ||
|
|
8bc2df6c8a | ||
|
|
7cd40d4d89 | ||
|
|
bebf23785d | ||
|
|
e78833cbcb | ||
|
|
b7ff636862 | ||
|
|
0288a6659c | ||
|
|
c7fd113f26 | ||
|
|
79241731e7 | ||
|
|
74e9914f5b | ||
|
|
65307f6eab | ||
|
|
e9f83aee90 | ||
|
|
fdaf743868 | ||
|
|
9d6b938ba9 | ||
|
|
1c8328f62d | ||
|
|
f24b82f345 | ||
|
|
37f1a7087e | ||
|
|
6bb654e630 | ||
|
|
fc260f8159 | ||
|
|
bf463926a3 | ||
|
|
c1673a1bbf | ||
|
|
7b44395e1a | ||
|
|
0f3529aab8 | ||
|
|
a72779997c | ||
|
|
49da536c7a | ||
|
|
c985c0a62b | ||
|
|
0f417b8434 | ||
|
|
4f0238122b | ||
|
|
52ff634f00 | ||
|
|
e820537fce | ||
|
|
7130d8a18c | ||
|
|
659d34dfc2 | ||
|
|
6a5c999628 | ||
|
|
3bcb44ea71 | ||
|
|
b108b4e71d | ||
|
|
a72f267558 | ||
|
|
cc75cebdb8 | ||
|
|
3a0510d6b4 | ||
|
|
0c4b88e562 | ||
|
|
ac3b0c2bad | ||
|
|
1823efa0e5 | ||
|
|
e5ce1760a6 | ||
|
|
e77a971519 | ||
|
|
d7715c90f0 | ||
|
|
8fe9bd7347 | ||
|
|
11d3d71c32 | ||
|
|
0462f4db63 | ||
|
|
120f1d6859 | ||
|
|
99ceb8dbc1 | ||
|
|
d7d044f717 | ||
|
|
53d892a0ba | ||
|
|
80e38f8669 | ||
|
|
3e76f6b054 | ||
|
|
55a3b76f45 | ||
|
|
bd9b767339 | ||
|
|
276a93c497 | ||
|
|
c6bdb67981 | ||
|
|
a6bb089633 | ||
|
|
606b00142f | ||
|
|
151ecf83e7 | ||
|
|
ccd71202de | ||
|
|
839aa9134c | ||
|
|
dcb5854557 | ||
|
|
ad9ca125a0 | ||
|
|
fe12b0e908 | ||
|
|
f733d22d55 | ||
|
|
9f8307a4ff | ||
|
|
a18f74a72a | ||
|
|
4d2b53c809 | ||
|
|
c02cd1f15b | ||
|
|
74139627e2 | ||
|
|
3f86bb0cd7 | ||
|
|
a81dfc271c | ||
|
|
470e08f165 | ||
|
|
5164762f2e | ||
|
|
6da1875ab6 | ||
|
|
3f72d35145 | ||
|
|
b26b9ea41b | ||
|
|
0539eda57e | ||
|
|
c5d72ad7cb | ||
|
|
bf7d9b5646 | ||
|
|
8ec6c7f0f7 | ||
|
|
2321122e81 | ||
|
|
bc439b45c9 | ||
|
|
e7d6783156 | ||
|
|
44e5682b1d | ||
|
|
f63918aa4e | ||
|
|
f42c677d5a | ||
|
|
5a56d64211 | ||
|
|
0bd1b3f45f | ||
|
|
3780587991 | ||
|
|
6875389948 | ||
|
|
0e5d6e79c5 | ||
|
|
490b74dd26 | ||
|
|
66d05e1b00 | ||
|
|
3d3101c3ab | ||
|
|
ccde4270d0 | ||
|
|
a1f799302e | ||
|
|
4619e257e8 | ||
|
|
c54a14cd3f | ||
|
|
a96d95c95b | ||
|
|
e61ca489ce | ||
|
|
c3ad5f0580 | ||
|
|
8b08f906bd | ||
|
|
4ad5f5ae37 | ||
|
|
68a6449339 | ||
|
|
b8d53b0f81 | ||
|
|
dbfd15b819 | ||
|
|
acd0cb119d | ||
|
|
d61bc4b5c1 | ||
|
|
5aa1146657 | ||
|
|
f15fd246a8 | ||
|
|
2b8547878a | ||
|
|
942d5d29d2 | ||
|
|
fde63a836d | ||
|
|
6102a0c115 | ||
|
|
4f4953206e | ||
|
|
f772ee7068 | ||
|
|
8f93e6bf5f | ||
|
|
64fefac194 | ||
|
|
d784b1290b | ||
|
|
0f2bc2fa25 | ||
|
|
0e856d2add | ||
|
|
acc587ce45 | ||
|
|
66180397d9 | ||
|
|
571c4b8d22 | ||
|
|
660ba3d722 | ||
|
|
414cb9bd7e | ||
|
|
e588efd0a1 | ||
|
|
1cdba5f73d | ||
|
|
cbccd10271 | ||
|
|
6de6b19944 | ||
|
|
5493b00957 | ||
|
|
81988a7fe7 | ||
|
|
b91ff09e27 | ||
|
|
945627d649 | ||
|
|
307c71ee07 | ||
|
|
e72932cbaa | ||
|
|
4347c2f81d | ||
|
|
619acfe0fe | ||
|
|
05765c2af9 | ||
|
|
728182de6c | ||
|
|
a19b5c4e05 | ||
|
|
f6895a0733 | ||
|
|
3e48b7a968 | ||
|
|
ebf65ecb96 | ||
|
|
37dab0928b | ||
|
|
aa0544cd3d | ||
|
|
f4b4cfc9de | ||
|
|
7785b2dbf9 | ||
|
|
e3dafb502b | ||
|
|
b30fc12135 | ||
|
|
28d204f2b1 | ||
|
|
1a3ff5ec41 | ||
|
|
2f6fd476a2 | ||
|
|
7a08452fa8 | ||
|
|
fa6bac3b43 | ||
|
|
2a60ff62d8 | ||
|
|
eaa4f193ce | ||
|
|
6973a0b71c | ||
|
|
bafd9ff85d | ||
|
|
4580033477 | ||
|
|
a4673021a9 | ||
|
|
8e2e143e6f | ||
|
|
f43f9171e5 | ||
|
|
3c8a8862ca | ||
|
|
969b89bb4d | ||
|
|
164b8970d3 | ||
|
|
34fd30e157 | ||
|
|
8e09f0cc15 | ||
|
|
693a4ef776 | ||
|
|
0992a989d4 | ||
|
|
1b137a8a8a | ||
|
|
7e8e86a77a | ||
|
|
20c1e2d7f2 | ||
|
|
7870c2706b | ||
|
|
5a5f50605b | ||
|
|
0106732cbd | ||
|
|
9ae269dd57 | ||
|
|
04ed47d545 | ||
|
|
6160535c03 | ||
|
|
8d92373c88 | ||
|
|
6bfc8f7d49 | ||
|
|
69f02eef82 | ||
|
|
02b8e54988 | ||
|
|
99472d2548 | ||
|
|
5a4c81bd75 | ||
|
|
ee09c0abda | ||
|
|
9baa79e10b | ||
|
|
a8909a3ce6 | ||
|
|
b9b9c2e5ff | ||
|
|
89adab6784 | ||
|
|
77d8afcfe6 | ||
|
|
4fcb063190 | ||
|
|
5deba15373 | ||
|
|
505426cd6a | ||
|
|
3cb9f37997 | ||
|
|
4c2f5c05e5 | ||
|
|
eefc9bd239 | ||
|
|
d18efdea73 | ||
|
|
a72a0ec0ce | ||
|
|
c7e9f30a9a | ||
|
|
6c404c8229 | ||
|
|
e5de530c2c | ||
|
|
3a87378847 | ||
|
|
6048a10d6d | ||
|
|
a5ad43b134 | ||
|
|
539f8fe5b5 | ||
|
|
569922805f | ||
|
|
3972e3de8a | ||
|
|
ba677a96aa | ||
|
|
d800e9a43e | ||
|
|
2d35a00caa | ||
|
|
dc5698b353 | ||
|
|
abada481b7 | ||
|
|
1e5eab0574 | ||
|
|
c1101af582 | ||
|
|
1db4c4fc8b | ||
|
|
bc949fe87a | ||
|
|
a890ee6612 | ||
|
|
90e0b5dcf0 | ||
|
|
9631988fc2 | ||
|
|
b5f80da28d | ||
|
|
7e0b943b70 | ||
|
|
425be32c15 | ||
|
|
f9a32e4abc | ||
|
|
2f4cd36595 | ||
|
|
70ee24d82a | ||
|
|
28576bbf49 | ||
|
|
7f9dfd3dae | ||
|
|
115aee2026 | ||
|
|
86fee6f04e | ||
|
|
2deab59b35 | ||
|
|
b4737457a8 | ||
|
|
305c770c58 | ||
|
|
afa9e23707 | ||
|
|
87fb5cf2ae | ||
|
|
3f8e00985c | ||
|
|
533928a4f1 | ||
|
|
b7048de2a1 | ||
|
|
2016eadb0d | ||
|
|
68b5bc0964 | ||
|
|
119fc5812b | ||
|
|
b628c1990e | ||
|
|
183bfa0ab2 | ||
|
|
b1fb867b6e | ||
|
|
673ba9f3cc | ||
|
|
cdd9a5ff4d | ||
|
|
d204e812e1 | ||
|
|
9163b9e4de | ||
|
|
ecd4da08ee | ||
|
|
525288d804 | ||
|
|
e829279e29 | ||
|
|
3d9555d420 | ||
|
|
5f7a1e769a | ||
|
|
8b118408fa | ||
|
|
de41845e3e | ||
|
|
61597585b5 | ||
|
|
e04b250a73 | ||
|
|
4fbe1b40e3 | ||
|
|
3ef5b576ac | ||
|
|
570b56364a | ||
|
|
ae4e8e2d8e | ||
|
|
2c8406d0ad | ||
|
|
94bd5ceed3 | ||
|
|
aa6be3d691 | ||
|
|
97fe65647a | ||
|
|
ee8b8866e0 | ||
|
|
3128a4c5c8 | ||
|
|
8ec6545bbc | ||
|
|
90a6850d76 | ||
|
|
16f70dc0ce | ||
|
|
f0ebc5e644 | ||
|
|
03c5dd78c1 | ||
|
|
e2b6e99a0c | ||
|
|
263aeef030 | ||
|
|
f809170c51 | ||
|
|
c2fcc0ac52 | ||
|
|
5e61fb0a14 | ||
|
|
cf222bd0c3 | ||
|
|
cb0c52fb26 | ||
|
|
c07c305384 | ||
|
|
d2fbf5bdea | ||
|
|
2d2a883b96 | ||
|
|
1f2fb3f796 | ||
|
|
8f3a4b98a5 | ||
|
|
70cf7431f7 | ||
|
|
f2ba86a62b | ||
|
|
292908f53f | ||
|
|
d621a5d2f3 | ||
|
|
75e8276784 | ||
|
|
67f49a0591 | ||
|
|
cceded2a0f | ||
|
|
846d3a85a2 | ||
|
|
7802da2b9c | ||
|
|
cd56a124d5 | ||
|
|
58a3662d0f | ||
|
|
6c7413e38c | ||
|
|
547e61a66b | ||
|
|
d246d1dece | ||
|
|
e2502e2e0c | ||
|
|
448cce38e1 | ||
|
|
dbc1e5ea3e | ||
|
|
a6ddc2496f | ||
|
|
d9a818279f | ||
|
|
6e2e613fee | ||
|
|
109aeb49e4 | ||
|
|
c892e9fa57 | ||
|
|
b2500557e7 | ||
|
|
7c311fbb55 | ||
|
|
f24388c1b5 | ||
|
|
3aef86bd34 | ||
|
|
c53a85cd50 | ||
|
|
448758a697 | ||
|
|
e51233bf9b | ||
|
|
f9cbe43627 | ||
|
|
5579817f9f | ||
|
|
51a5f58258 | ||
|
|
388ad4e840 | ||
|
|
48a8d9ae35 | ||
|
|
dd6003bd4f | ||
|
|
fba407f3b6 | ||
|
|
88b406544b | ||
|
|
3438ed94ce | ||
|
|
ec71b21264 | ||
|
|
b223f5f16e | ||
|
|
0a64e4c918 | ||
|
|
9b41db962e | ||
|
|
43d3c7b5d7 | ||
|
|
8168089591 | ||
|
|
6b55fc3032 | ||
|
|
87ab42b155 | ||
|
|
98130e89de | ||
|
|
121f0e3628 | ||
|
|
8a3d88b3ce | ||
|
|
b8b41fe847 | ||
|
|
5bbef3ee16 | ||
|
|
9a2b6c8ec9 | ||
|
|
5272c99643 | ||
|
|
43e9515a03 | ||
|
|
7e9b7398c8 | ||
|
|
58d7b001a5 | ||
|
|
a259560d29 | ||
|
|
22c746543a | ||
|
|
bcbc2738ca | ||
|
|
604e3b6892 |
@@ -7,6 +7,12 @@
|
||||
"commands": [
|
||||
"dotnet-format"
|
||||
]
|
||||
},
|
||||
"cake.tool": {
|
||||
"version": "2.2.0",
|
||||
"commands": [
|
||||
"dotnet-cake"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
37
.github/CODEOWNERS
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Please sort lines alphabetically, this will ensure we don't accidentally add duplicates.
|
||||
#
|
||||
# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
|
||||
|
||||
# The following owners will be the default owners for everything in the repo.
|
||||
# Unless a later match takes precedence
|
||||
# @bitwarden/tech-leads
|
||||
|
||||
@bitwarden/dept-development-mobile
|
||||
|
||||
## Auth team files ##
|
||||
|
||||
## Platform team files ##
|
||||
appIcons @bitwarden/team-platform-dev
|
||||
build.cake @bitwarden/team-platform-dev
|
||||
|
||||
## Vault team files ##
|
||||
src/watchOS @bitwarden/team-vault-dev
|
||||
|
||||
## Tools team files ##
|
||||
src/Core/Services/EmailForwarders @bitwarden/team-tools-dev
|
||||
|
||||
## Crowdin Sync files ##
|
||||
src/App/Resources @bitwarden/team-tools-dev
|
||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization @bitwarden/team-tools-dev
|
||||
store/apple @bitwarden/team-tools-dev
|
||||
store/google @bitwarden/team-tools-dev
|
||||
|
||||
## Locales ##
|
||||
src/App/Resources/AppResources.Designer.cs
|
||||
src/App/Resources/AppResources.resx
|
||||
src/watchOS/bitwarden/bitwarden\ WatchKit\ Extension/Localization/en.lproj
|
||||
store/apple/en
|
||||
store/google/en
|
||||
|
||||
## Utils ##
|
||||
store/google/Publisher
|
||||
6
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Customer Support
|
||||
url: https://bitwarden.com/contact/
|
||||
about: Please contact our customer support for account issues and general customer support.
|
||||
- name: Report mobile autofill failure
|
||||
url: https://docs.google.com/forms/d/e/1FAIpQLScMopHyN7KGJs8hW562VTzbIGL4KcFnx0wJcsW0GYE1BnPiGA/viewform
|
||||
about: We are aware of some situations where the Bitwarden mobile app will not autofill information correctly. This is something the Bitwarden team is actively working on but need your help as a community and active Bitwarden users!
|
||||
@@ -9,9 +12,6 @@ contact_links:
|
||||
- name: Bitwarden Community Forums
|
||||
url: https://community.bitwarden.com
|
||||
about: Please visit the community forums for general community discussion, support and the development roadmap.
|
||||
- name: Customer Support
|
||||
url: https://bitwarden.com/contact/
|
||||
about: Please contact our customer support for account issues and general customer support.
|
||||
- name: Security Issues
|
||||
url: https://hackerone.com/bitwarden
|
||||
about: We use HackerOne to manage security disclosures.
|
||||
|
||||
12
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -21,12 +21,8 @@
|
||||
|
||||
|
||||
|
||||
## Testing requirements
|
||||
<!--What functionality requires testing by QA? This includes testing new behavior and regression testing-->
|
||||
|
||||
|
||||
|
||||
## Before you submit
|
||||
- [ ] I have added **unit tests** where it makes sense to do so (encouraged but not required)
|
||||
- [ ] This change requires a **documentation update** (notify the documentation team)
|
||||
- [ ] This change has particular **deployment requirements** (notify the DevOps team)
|
||||
- Please check for formatting errors (`dotnet format --verify-no-changes`) (required)
|
||||
- Please add **unit tests** where it makes sense to do so (encouraged but not required)
|
||||
- If this change requires a **documentation update** - notify the documentation team
|
||||
- If this change has particular **deployment requirements** - notify the DevOps team
|
||||
|
||||
19
.github/labeler.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
android:
|
||||
- src/App/*
|
||||
- src/Core/*
|
||||
- src/Android/*
|
||||
|
||||
iOS:
|
||||
- src/App/*
|
||||
- src/Core/*
|
||||
- lib/ios/*
|
||||
- src/iOS/*
|
||||
- 'src/iOS.Autofill/*'
|
||||
- 'src/iOS.Core/*'
|
||||
- 'src/iOS.Extension/*'
|
||||
- 'src/iOS.ShareExtension/*'
|
||||
- 'src/iOS.Widget/*'
|
||||
- src/watchOS/*
|
||||
|
||||
watchOS:
|
||||
- src/watchOS/*
|
||||
37
.github/renovate.json
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
":combinePatchMinorReleases",
|
||||
":dependencyDashboard",
|
||||
":maintainLockFilesWeekly",
|
||||
":pinAllExceptPeerDependencies",
|
||||
":prConcurrentLimit10",
|
||||
":rebaseStalePrs",
|
||||
"schedule:weekends",
|
||||
":separateMajorReleases"
|
||||
],
|
||||
"enabledManagers": ["cargo", "github-actions", "npm", "nuget"],
|
||||
"packageRules": [
|
||||
{
|
||||
"groupName": "cargo minor",
|
||||
"matchManagers": ["cargo"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "gh minor",
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "npm minor",
|
||||
"matchManagers": ["npm"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "nuget minor",
|
||||
"matchManagers": ["nuget"],
|
||||
"matchUpdateTypes": ["minor", "patch"]
|
||||
},
|
||||
]
|
||||
}
|
||||
12
.github/resources/export-options-app-store.plist
vendored
@@ -7,13 +7,17 @@
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>com.8bit.bitwarden</key>
|
||||
<string>Dist: Bitwarden 2021</string>
|
||||
<string>Dist: Bitwarden</string>
|
||||
<key>com.8bit.bitwarden.autofill</key>
|
||||
<string>Dist: Autofill 2021</string>
|
||||
<string>Dist: Autofill</string>
|
||||
<key>com.8bit.bitwarden.find-login-action-extension</key>
|
||||
<string>Dist: Extension 2021</string>
|
||||
<string>Dist: Extension</string>
|
||||
<key>com.8bit.bitwarden.share-extension</key>
|
||||
<string>Dist: Share Extension 2021</string>
|
||||
<string>Dist: Share Extension</string>
|
||||
<key>com.8bit.bitwarden.watchkitapp</key>
|
||||
<string>Dist: Bitwarden Watch App</string>
|
||||
<key>com.8bit.bitwarden.watchkitapp.watchkitextension</key>
|
||||
<string>Dist: Bitwarden Watch App Extension</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
BIN
.github/secrets/GoogleService-Info.plist.gpg
vendored
Normal file
BIN
.github/secrets/dist_autofill.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_bitwarden.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_extension.mobileprovision.gpg
vendored
BIN
.github/secrets/dist_watch_app.mobileprovision.gpg
vendored
Normal file
BIN
.github/secrets/dist_watch_app_extension.mobileprovision.gpg
vendored
Normal file
BIN
.github/secrets/iphone-distribution-cert.p12.gpg
vendored
64
.github/workflows/automatic-issue-responses.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: Automatic responses
|
||||
on:
|
||||
issues:
|
||||
types:
|
||||
- labeled
|
||||
jobs:
|
||||
close-issue:
|
||||
name: 'Close issue with automatic response'
|
||||
runs-on: ubuntu-20.04
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
# Feature request
|
||||
- if: github.event.label.name == 'feature-request'
|
||||
name: Feature request
|
||||
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
|
||||
with:
|
||||
comment: |
|
||||
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
|
||||
|
||||
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
|
||||
|
||||
This issue will now be closed. Thanks!
|
||||
# Intended behavior
|
||||
- if: github.event.label.name == 'intended-behavior'
|
||||
name: Intended behaviour
|
||||
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
|
||||
with:
|
||||
comment: |
|
||||
Your issue appears to be describing the intended behavior of the software. If you want this to be changed, it would be a feature request.
|
||||
|
||||
We use GitHub issues as a place to track bugs and other development related issues. The [Bitwarden Community Forums](https://community.bitwarden.com/) has a [Feature Requests](https://community.bitwarden.com/c/feature-requests) section for submitting, voting for, and discussing requests like this one.
|
||||
|
||||
Please [sign up on our forums](https://community.bitwarden.com/signup) and search to see if this request already exists. If so, you can vote for it and contribute to any discussions about it. If not, you can re-create the request there so that it can be properly tracked.
|
||||
|
||||
This issue will now be closed. Thanks!
|
||||
# Customer support request
|
||||
- if: github.event.label.name == 'customer-support'
|
||||
name: Customer Support request
|
||||
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
|
||||
with:
|
||||
comment: |
|
||||
We use GitHub issues as a place to track bugs and other development related issues. Your issue appears to be a support request, or would otherwise be better handled by our dedicated Customer Success team.
|
||||
|
||||
Please contact us using our [Contact page](https://bitwarden.com/contact). You can include a link to this issue in the message content.
|
||||
|
||||
Alternatively, you can also search for an answer in our [help documentation](https://bitwarden.com/help/) or get help from other Bitwarden users on our [community forums](https://community.bitwarden.com/c/support/). The issue here will be closed.
|
||||
# Resolved
|
||||
- if: github.event.label.name == 'resolved'
|
||||
name: Resolved
|
||||
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
|
||||
with:
|
||||
comment: |
|
||||
We’ve closed this issue, as it appears the original problem has been resolved. If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
|
||||
# Stale
|
||||
- if: github.event.label.name == 'stale'
|
||||
name: Stale
|
||||
uses: peter-evans/close-issue@276d7966e389d888f011539a86c8920025ea0626 # v3.0.1
|
||||
with:
|
||||
comment: |
|
||||
As we haven’t heard from you about this problem in some time, this issue will now be closed.
|
||||
|
||||
If this happens again or continues to be an problem, please respond to this issue with any additional detail to assist with reproduction and root cause analysis.
|
||||
390
.github/workflows/build.yml
vendored
@@ -4,20 +4,19 @@ name: Build
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'l10n_master'
|
||||
- 'gh-pages'
|
||||
- "l10n_master"
|
||||
- "gh-pages"
|
||||
paths-ignore:
|
||||
- '.github/workflows/**'
|
||||
- ".github/workflows/**"
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
|
||||
jobs:
|
||||
cloc:
|
||||
name: CLOC
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Set up CLOC
|
||||
run: |
|
||||
@@ -30,38 +29,62 @@ jobs:
|
||||
|
||||
setup:
|
||||
name: Setup
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
rc_branch_exists: ${{ steps.branch-check.outputs.rc_branch_exists }}
|
||||
hotfix_branch_exists: ${{ steps.branch-check.outputs.hotfix_branch_exists }}
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
submodules: 'true'
|
||||
|
||||
- name: Check if special branches exist
|
||||
id: branch-check
|
||||
run: |
|
||||
if [[ $(git ls-remote --heads origin rc) ]]; then
|
||||
echo "::set-output name=rc_branch_exists::1"
|
||||
echo "rc_branch_exists=1" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::set-output name=rc_branch_exists::0"
|
||||
echo "rc_branch_exists=0" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
if [[ $(git ls-remote --heads origin hotfix-rc) ]]; then
|
||||
echo "::set-output name=hotfix_branch_exists::1"
|
||||
echo "hotfix_branch_exists=1" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "::set-output name=hotfix_branch_exists::0"
|
||||
echo "hotfix_branch_exists=0" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
|
||||
android:
|
||||
name: Android
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
needs: setup
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
variant: ["prod", "qa"]
|
||||
steps:
|
||||
- name: Setup NuGet
|
||||
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
|
||||
with:
|
||||
nuget-version: 5.9.0
|
||||
|
||||
- name: Set up .NET
|
||||
uses: actions/setup-dotnet@3447fd6a9f9e57506b15f895c5b76d3b197dc7c2 # v3.2.0
|
||||
with:
|
||||
dotnet-version: '3.1.x'
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||
|
||||
- name: Setup Windows builder
|
||||
run: choco install checksum --no-progress
|
||||
|
||||
- name: Install Microsoft OpenJDK 11
|
||||
run: |
|
||||
choco install microsoft-openjdk11 --no-progress
|
||||
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
Write-Output "Java Home: $env:JAVA_HOME"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@@ -72,7 +95,9 @@ jobs:
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Decrypt secrets
|
||||
env:
|
||||
@@ -84,12 +109,19 @@ jobs:
|
||||
--output ./src/Android/app_play-keystore.jks ./.github/secrets/app_play-keystore.jks.gpg
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output ./src/Android/app_upload-keystore.jks ./.github/secrets/app_upload-keystore.jks.gpg
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output $HOME/secrets/play_creds.json ./.github/secrets/play_creds.json.gpg
|
||||
shell: bash
|
||||
|
||||
- name: Decrypt secrets - Google Services
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
env:
|
||||
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
|
||||
run: |
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output ./src/Android/google-services.json ./.github/secrets/google-services.json.gpg
|
||||
shell: bash
|
||||
|
||||
- name: Increment version
|
||||
run: |
|
||||
BUILD_NUMBER=$((3000 + $GITHUB_RUN_NUMBER))
|
||||
@@ -107,36 +139,50 @@ jobs:
|
||||
|
||||
- name: Restore tools
|
||||
run: dotnet tool restore
|
||||
shell: pwsh
|
||||
|
||||
- name: Verify Format
|
||||
run: dotnet tool run dotnet-format --check
|
||||
shell: pwsh
|
||||
|
||||
- name: Run Core tests
|
||||
run: dotnet test test/Core.Test/Core.Test.csproj
|
||||
run: dotnet test test/Core.Test/Core.Test.csproj --logger "trx;LogFileName=test-results.trx"
|
||||
|
||||
- name: Report test results
|
||||
uses: dorny/test-reporter@c9b3d0e2bd2a4e96aaf424dbaa31c46b42318226 # v1.6.0
|
||||
if: always()
|
||||
with:
|
||||
name: Test Results
|
||||
path: "**/test-results.trx"
|
||||
reporter: dotnet-trx
|
||||
fail-on-error: true
|
||||
|
||||
- name: Build Play Store publisher
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
|
||||
|
||||
- name: Build for Play Store
|
||||
- name: Setup Android build (${{ matrix.variant }})
|
||||
run: dotnet cake build.cake --target Android --variant ${{ matrix.variant }}
|
||||
|
||||
- name: Build Android
|
||||
run: |
|
||||
$configuration = "Release";
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Build $configuration Configuration"
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
||||
shell: pwsh
|
||||
|
||||
- name: Sign for Play Store
|
||||
- name: Sign Android Build
|
||||
env:
|
||||
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
|
||||
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
|
||||
run: |
|
||||
$androidPath = $($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj");
|
||||
$packageName = "com.x8bit.bitwarden";
|
||||
|
||||
if ("${{ matrix.variant }}" -ne "prod")
|
||||
{
|
||||
$packageName = "com.x8bit.bitwarden.${{ matrix.variant }}";
|
||||
}
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Sign Google Play Bundle Release Configuration"
|
||||
Write-Output "########################################"
|
||||
@@ -150,9 +196,8 @@ jobs:
|
||||
Write-Output "##### Copy Google Play Bundle to project root"
|
||||
Write-Output "########################################"
|
||||
|
||||
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
|
||||
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.aab");
|
||||
|
||||
$signedAabPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.aab");
|
||||
$signedAabDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).aab");
|
||||
Copy-Item $signedAabPath $signedAabDestPath
|
||||
|
||||
Write-Output "########################################"
|
||||
@@ -168,33 +213,69 @@ jobs:
|
||||
Write-Output "##### Copy Release APK to project root"
|
||||
Write-Output "########################################"
|
||||
|
||||
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
|
||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden.apk");
|
||||
$signedApkPath = $($env:GITHUB_WORKSPACE + "/src/Android/bin/Release/$($packageName)-Signed.apk");
|
||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/$($packageName).apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload Play Store .aab artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
- name: Upload Prod .aab artifact
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.aab
|
||||
path: ./com.x8bit.bitwarden.aab
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Play Store .apk artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
- name: Upload Prod .apk artifact
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.apk
|
||||
path: ./com.x8bit.bitwarden.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Other .apk artifact
|
||||
if: ${{ matrix.variant != 'prod' }}
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||
path: ./com.x8bit.bitwarden.${{ matrix.variant }}.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for Prod .apk artifact
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
run: |
|
||||
checksum -f="./com.x8bit.bitwarden.apk" `
|
||||
-t sha256 | Out-File -Encoding ASCII ./bw-android-apk-sha256.txt
|
||||
|
||||
- name: Create checksum for Other .apk artifact
|
||||
if: ${{ matrix.variant != 'prod' }}
|
||||
run: |
|
||||
checksum -f="./com.x8bit.bitwarden.${{ matrix.variant }}.apk" `
|
||||
-t sha256 | Out-File -Encoding ASCII ./bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
|
||||
- name: Upload .apk sha file for prod
|
||||
if: ${{ matrix.variant == 'prod' }}
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-android-apk-sha256.txt
|
||||
path: ./bw-android-apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .apk sha file for other
|
||||
if: ${{ matrix.variant != 'prod' }}
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
path: ./bw-android-${{ matrix.variant }}-apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Deploy to Play Store
|
||||
if: |
|
||||
(github.ref == 'refs/heads/master'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
if: ${{ matrix.variant == 'prod' && (( github.ref == 'refs/heads/main'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc' ) }}
|
||||
run: |
|
||||
PUBLISHER_PATH="$GITHUB_WORKSPACE/store/google/Publisher/bin/Release/netcoreapp3.1/Publisher.dll"
|
||||
CREDS_PATH="$HOME/secrets/play_creds.json"
|
||||
@@ -207,10 +288,24 @@ jobs:
|
||||
|
||||
f-droid:
|
||||
name: F-Droid Build
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Setup NuGet
|
||||
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
|
||||
with:
|
||||
nuget-version: 5.9.0
|
||||
|
||||
- name: Set up MSBuild
|
||||
uses: microsoft/setup-msbuild@ab534842b4bdf384b8aaf93765dc6f721d9f5fab
|
||||
uses: microsoft/setup-msbuild@1ff57057b5cfdc39105cd07a01d78e9b0ea0c14c # v1.3.1
|
||||
|
||||
- name: Setup Windows builder
|
||||
run: choco install checksum --no-progress
|
||||
|
||||
- name: Install Microsoft OpenJDK 11
|
||||
run: |
|
||||
choco install microsoft-openjdk11 --no-progress
|
||||
Write-Output "JAVA_HOME=$(Get-ChildItem -Path 'C:\Program Files\Microsoft\jdk*' | Select -First 1 -ExpandProperty FullName)" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
Write-Output "Java Home: $env:JAVA_HOME"
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
@@ -221,7 +316,7 @@ jobs:
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Decrypt secrets
|
||||
env:
|
||||
@@ -304,18 +399,6 @@ jobs:
|
||||
|
||||
$xml.Save($androidPath);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from App.csproj"
|
||||
Write-Output "########################################"
|
||||
|
||||
$xml=New-Object XML;
|
||||
$xml.Load($appPath);
|
||||
|
||||
$appCenterNode=$xml.SelectSingleNode("/Project/ItemGroup/PackageReference[@Include='Microsoft.AppCenter.Crashes']");
|
||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||
|
||||
$xml.Save($appPath);
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Uninstall from Core.csproj"
|
||||
Write-Output "########################################"
|
||||
@@ -327,7 +410,6 @@ jobs:
|
||||
$appCenterNode.ParentNode.RemoveChild($appCenterNode);
|
||||
|
||||
$xml.Save($corePath);
|
||||
shell: pwsh
|
||||
|
||||
- name: Restore packages
|
||||
run: nuget restore
|
||||
@@ -341,7 +423,6 @@ jobs:
|
||||
Write-Output "########################################"
|
||||
|
||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/Android/Android.csproj")" "/p:Configuration=$configuration"
|
||||
shell: pwsh
|
||||
|
||||
- name: Sign for F-Droid
|
||||
env:
|
||||
@@ -365,21 +446,37 @@ jobs:
|
||||
$signedApkDestPath = $($env:GITHUB_WORKSPACE + "/com.x8bit.bitwarden-fdroid.apk");
|
||||
|
||||
Copy-Item $signedApkPath $signedApkDestPath
|
||||
shell: pwsh
|
||||
|
||||
- name: Upload F-Droid .apk artifact
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
path: ./com.x8bit.bitwarden-fdroid.apk
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Create checksum for F-Droid artifact
|
||||
run: |
|
||||
checksum -f="./com.x8bit.bitwarden-fdroid.apk" `
|
||||
-t sha256 | Out-File -Encoding ASCII ./bw-fdroid-apk-sha256.txt
|
||||
|
||||
- name: Upload F-Droid sha file
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: bw-fdroid-apk-sha256.txt
|
||||
path: ./bw-fdroid-apk-sha256.txt
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
ios:
|
||||
name: Apple iOS
|
||||
runs-on: macos-11
|
||||
runs-on: macos-12
|
||||
needs: setup
|
||||
steps:
|
||||
- name: Setup NuGet
|
||||
uses: nuget/setup-nuget@296fd3ccf8528660c91106efefe2364482f86d6f # v1.2.0
|
||||
with:
|
||||
nuget-version: 5.9.0
|
||||
|
||||
- name: Print environment
|
||||
run: |
|
||||
nuget help | grep Version
|
||||
@@ -389,18 +486,20 @@ jobs:
|
||||
echo "GitHub event: $GITHUB_EVENT"
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
submodules: 'true'
|
||||
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "appcenter-ios-token"
|
||||
|
||||
- name: Decrypt secrets
|
||||
@@ -422,7 +521,14 @@ jobs:
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output $HOME/secrets/dist_share_extension.mobileprovision \
|
||||
./.github/secrets/dist_share_extension.mobileprovision.gpg
|
||||
shell: bash
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output $HOME/secrets/dist_watch_app.mobileprovision \
|
||||
./.github/secrets/dist_watch_app.mobileprovision.gpg
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output $HOME/secrets/dist_watch_app_extension.mobileprovision \
|
||||
./.github/secrets/dist_watch_app_extension.mobileprovision.gpg
|
||||
gpg --quiet --batch --yes --decrypt --passphrase="$DECRYPT_FILE_PASSWORD" \
|
||||
--output ./src/watchOS/bitwarden/GoogleService-Info.plist ./.github/secrets/GoogleService-Info.plist.gpg
|
||||
|
||||
- name: Increment version
|
||||
run: |
|
||||
@@ -436,7 +542,8 @@ jobs:
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Extension/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.Autofill/Info.plist
|
||||
perl -0777 -pi.bak -e 's/<key>CFBundleVersion<\/key>\s*<string>1<\/string>/<key>CFBundleVersion<\/key>\n\t<string>'"$BUILD_NUMBER"'<\/string>/' ./src/iOS.ShareExtension/Info.plist
|
||||
shell: bash
|
||||
cd src/watchOS/bitwarden
|
||||
agvtool new-version -all $BUILD_NUMBER
|
||||
|
||||
- name: Update Entitlements
|
||||
run: |
|
||||
@@ -445,7 +552,6 @@ jobs:
|
||||
echo "########################################"
|
||||
|
||||
perl -0777 -pi.bak -e 's/<key>aps-environment<\/key>\s*<string>development<\/string>/<key>aps-environment<\/key>\n\t<string>production<\/string>/' ./src/iOS/Entitlements.plist
|
||||
shell: bash
|
||||
|
||||
- name: Set up Keychain
|
||||
env:
|
||||
@@ -462,7 +568,6 @@ jobs:
|
||||
security import ~/secrets/iphone-distribution-cert.p12 -k build.keychain -P $DIST_CERT_PASSWORD \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain
|
||||
shell: bash
|
||||
|
||||
- name: Set up provisioning profiles
|
||||
run: |
|
||||
@@ -470,6 +575,8 @@ jobs:
|
||||
BITWARDEN_PROFILE_PATH=$HOME/secrets/dist_bitwarden.mobileprovision
|
||||
EXTENSION_PROFILE_PATH=$HOME/secrets/dist_extension.mobileprovision
|
||||
SHARE_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_share_extension.mobileprovision
|
||||
WATCH_APP_PROFILE_PATH=$HOME/secrets/dist_watch_app.mobileprovision
|
||||
WATCH_APP_EXTENSION_PROFILE_PATH=$HOME/secrets/dist_watch_app_extension.mobileprovision
|
||||
PROFILES_DIR_PATH=$HOME/Library/MobileDevice/Provisioning\ Profiles
|
||||
|
||||
mkdir -p "$PROFILES_DIR_PATH"
|
||||
@@ -485,7 +592,24 @@ jobs:
|
||||
|
||||
SHARE_EXTENSION_UUID=$(grep UUID -A1 -a $SHARE_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
||||
cp $SHARE_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$SHARE_EXTENSION_UUID.mobileprovision"
|
||||
shell: bash
|
||||
|
||||
WATCH_APP_UUID=$(grep UUID -A1 -a $WATCH_APP_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
||||
cp $WATCH_APP_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_UUID.mobileprovision"
|
||||
|
||||
WATCH_APP_EXTENSION_UUID=$(grep UUID -A1 -a $WATCH_APP_EXTENSION_PROFILE_PATH | grep -io "[-A-F0-9]\{36\}")
|
||||
cp $WATCH_APP_EXTENSION_PROFILE_PATH "$PROFILES_DIR_PATH/$WATCH_APP_EXTENSION_UUID.mobileprovision"
|
||||
|
||||
- name: Bulid WatchApp
|
||||
run: |
|
||||
echo "########################################"
|
||||
echo "##### Build WatchApp with Release Configuration"
|
||||
echo "########################################"
|
||||
|
||||
xcodebuild archive -workspace ./src/watchOS/bitwarden/bitwarden.xcodeproj/project.xcworkspace -configuration Release -scheme bitwarden\ WatchKit\ App -archivePath ./src/watchOS/bitwarden
|
||||
|
||||
echo "########################################"
|
||||
echo "##### Done"
|
||||
echo "########################################"
|
||||
|
||||
- name: Restore packages
|
||||
run: nuget restore
|
||||
@@ -495,6 +619,22 @@ jobs:
|
||||
$configuration = "AppStore";
|
||||
$platform = "iPhone";
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
||||
Write-Output "########################################"
|
||||
msbuild "$($env:GITHUB_WORKSPACE + "/src/iOS/iOS.csproj")" "/p:Platform=$platform" `
|
||||
"/p:Configuration=$configuration" "/p:ArchiveOnBuild=true" "/t:`"Build`""
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Done"
|
||||
Write-Output "########################################"
|
||||
shell: pwsh
|
||||
|
||||
- name: Archive Build for Mobile Automation
|
||||
run: |
|
||||
$configuration = "Release";
|
||||
$platform = "iPhoneSimulator";
|
||||
|
||||
Write-Output "########################################"
|
||||
Write-Output "##### Archive $configuration Configuration for $platform Platform"
|
||||
Write-Output "########################################"
|
||||
@@ -515,18 +655,29 @@ jobs:
|
||||
|
||||
xcodebuild -exportArchive -archivePath $ARCHIVE_PATH -exportPath $EXPORT_PATH \
|
||||
-exportOptionsPlist $EXPORT_OPTIONS_PATH
|
||||
shell: bash
|
||||
|
||||
- name: Export .app for Automation CI
|
||||
run: |
|
||||
ARCHIVE_PATH="./src/iOS/bin/iPhoneSimulator/Release/BitwardeniOS.app"
|
||||
EXPORT_PATH="./bitwarden-export"
|
||||
|
||||
zip -r -q BitwardeniOS.app.zip $ARCHIVE_PATH
|
||||
mv BitwardeniOS.app.zip $EXPORT_PATH
|
||||
|
||||
- name: Copy all dSYMs files to upload
|
||||
run: |
|
||||
ARCHIVE_DSYMS_PATH="$HOME/Library/Developer/Xcode/Archives/*/*.xcarchive/dSYMs"
|
||||
EXPORT_PATH="./bitwarden-export"
|
||||
|
||||
cp -r $ARCHIVE_DSYMS_PATH $EXPORT_PATH
|
||||
shell: bash
|
||||
WATCH_ARCHIVE_DSYMS_PATH="./src/watchOS/bitwarden.xcarchive/dSYMs/"
|
||||
WATCH_DSYMS_EXPORT_PATH="$EXPORT_PATH/Watch_dSYMs"
|
||||
|
||||
cp -r -v $ARCHIVE_DSYMS_PATH $EXPORT_PATH
|
||||
mkdir $WATCH_DSYMS_EXPORT_PATH
|
||||
cp -r -v $WATCH_ARCHIVE_DSYMS_PATH $WATCH_DSYMS_EXPORT_PATH
|
||||
|
||||
- name: Upload App Store .ipa & dSYMs artifacts
|
||||
uses: actions/upload-artifact@6673cd052c4cd6fcf4b4e6e60ea986c889389535
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: Bitwarden iOS
|
||||
path: |
|
||||
@@ -534,9 +685,16 @@ jobs:
|
||||
./bitwarden-export/dSYMs/*.*
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .app file for Automation CI
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
with:
|
||||
name: BitwardeniOS.app.zip
|
||||
path: ./bitwarden-export/BitwardeniOS.app.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Install AppCenter CLI
|
||||
if: |
|
||||
(github.ref == 'refs/heads/master'
|
||||
(github.ref == 'refs/heads/main'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
@@ -545,66 +703,78 @@ jobs:
|
||||
|
||||
- name: Upload dSYMs to App Center
|
||||
if: |
|
||||
(github.ref == 'refs/heads/master'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
(github.ref == 'refs/heads/main'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
env:
|
||||
APPCENTER_IOS_TOKEN: ${{ steps.retrieve-secrets.outputs.appcenter-ios-token }}
|
||||
run: appcenter crashes upload-symbols -a bitwarden/bitwarden -s "./bitwarden-export/dSYMs" --token $APPCENTER_IOS_TOKEN
|
||||
shell: bash
|
||||
|
||||
- name: Upload Watch dSYMs to Firebase Crashlytics
|
||||
if: |
|
||||
(github.ref == 'refs/heads/main'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
run: |
|
||||
echo "########################################"
|
||||
echo "##### Uploading Watch dSYMs to Firebase"
|
||||
echo "########################################"
|
||||
|
||||
find "$HOME/Library/Developer/XCode/DerivedData" -name "upload-symbols" -exec chmod +x {} \; -exec {} -gsp "./src/watchOS/bitwarden/GoogleService-Info.plist" -p ios "./bitwarden-export/Watch_dSYMs" \;
|
||||
|
||||
- name: Deploy to App Store
|
||||
if: |
|
||||
(github.ref == 'refs/heads/master'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
(github.ref == 'refs/heads/main'
|
||||
&& needs.setup.outputs.rc_branch_exists == 0
|
||||
&& needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| (github.ref == 'refs/heads/rc' && needs.setup.outputs.hotfix_branch_exists == 0)
|
||||
|| github.ref == 'refs/heads/hotfix-rc'
|
||||
env:
|
||||
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
|
||||
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
|
||||
run: |
|
||||
xcrun altool --upload-app --type ios --file "./bitwarden-export/Bitwarden.ipa" \
|
||||
--username "$APPLE_ID_USERNAME" --password "$APPLE_ID_PASSWORD"
|
||||
shell: bash
|
||||
|
||||
|
||||
crowdin-push:
|
||||
name: Crowdin Push
|
||||
if: github.ref == 'refs/heads/master'
|
||||
if: github.ref == 'refs/heads/main'
|
||||
needs:
|
||||
- android
|
||||
- f-droid
|
||||
- ios
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@a12a3943b4bdde767164f792f33f40b04645d846
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token"
|
||||
|
||||
- name: Upload Sources
|
||||
uses: crowdin/github-action@9237b4cb361788dfce63feb2e2f15c09e2fe7415
|
||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
crowdin_branch_name: master
|
||||
crowdin_branch_name: main
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
|
||||
@@ -612,7 +782,7 @@ jobs:
|
||||
check-failures:
|
||||
name: Check for failures
|
||||
if: always()
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
needs:
|
||||
- cloc
|
||||
- android
|
||||
@@ -622,9 +792,9 @@ jobs:
|
||||
steps:
|
||||
- name: Check if any job failed
|
||||
if: |
|
||||
(github.ref == 'refs/heads/master')
|
||||
|| (github.ref == 'refs/heads/rc')
|
||||
|| (github.ref == 'refs/heads/hotfix-rc')
|
||||
(github.ref == 'refs/heads/main')
|
||||
|| (github.ref == 'refs/heads/rc')
|
||||
|| (github.ref == 'refs/heads/hotfix-rc')
|
||||
env:
|
||||
CLOC_STATUS: ${{ needs.cloc.result }}
|
||||
ANDROID_STATUS: ${{ needs.android.result }}
|
||||
@@ -644,22 +814,22 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Login to Azure - Prod Subscription
|
||||
uses: Azure/login@1f63701bf3e6892515f1b7ce2d2bf1708b46beaf
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
if: failure()
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@b5c723b9ac7870c022b8c35befe620b7009b336f
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
if: failure()
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "devops-alerts-slack-webhook-url"
|
||||
|
||||
- name: Notify Slack on failure
|
||||
uses: act10ns/slack@da3191ebe2e67f49b46880b4633f5591a96d1d33
|
||||
uses: act10ns/slack@ed1309ab9862e57e9e583e51c7889486b9a00b0f # v2.0.0
|
||||
if: failure()
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ steps.retrieve-secrets.outputs.devops-alerts-slack-webhook-url }}
|
||||
|
||||
24
.github/workflows/crowdin-pull.yml
vendored
@@ -15,35 +15,37 @@ jobs:
|
||||
_CROWDIN_PROJECT_ID: "269690"
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@5a4ac9002d0be2fb38bd78e4b4dbde5606d7042f # v2.3.4
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
|
||||
|
||||
- name: Login to Azure
|
||||
uses: Azure/login@77f1b2e3fb80c0e8645114159d17008b8a2e475a
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.6
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_PROD_KV_CREDENTIALS }}
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: Azure/get-keyvault-secrets@80ccd3fafe5662407cc2e55f202ee34bfff8c403
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-prod-kv"
|
||||
secrets: "crowdin-api-token"
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "crowdin-api-token, github-gpg-private-key, github-gpg-private-key-passphrase"
|
||||
|
||||
- name: Download translations
|
||||
uses: crowdin/github-action@e39093fd75daae7859c68eded4b43d42ec78d8ea
|
||||
uses: crowdin/github-action@965d501f160af7b1f88aed4c29154b0caf1e94b9 # v1.9.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CROWDIN_API_TOKEN: ${{ steps.retrieve-secrets.outputs.crowdin-api-token }}
|
||||
with:
|
||||
config: crowdin.yml
|
||||
crowdin_branch_name: master
|
||||
crowdin_branch_name: main
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
github_user_name: "github-actions"
|
||||
github_user_email: "<>"
|
||||
github_user_name: "bitwarden-devops-bot"
|
||||
github_user_email: "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||
commit_message: "Autosync the updated translations"
|
||||
localization_branch_name: crowdin-auto-sync
|
||||
create_pull_request: true
|
||||
pull_request_title: "Autosync Crowdin Translations"
|
||||
pull_request_body: "Autosync the updated translations"
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
gpg_passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
|
||||
6
.github/workflows/enforce-labels.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Enforce Label
|
||||
uses: yogevbd/enforce-label-action@8d1e1709b1011e6d90400a0e6cf7c0b77aa5efeb
|
||||
uses: yogevbd/enforce-label-action@a3c219da6b8fa73f6ba62b68ff09c469b3a1c024 # 2.2.2
|
||||
with:
|
||||
BANNED_LABELS: "hold"
|
||||
BANNED_LABELS_DESCRIPTION: "PRs on hold cannot be merged"
|
||||
BANNED_LABELS: "hold,needs-qa"
|
||||
BANNED_LABELS_DESCRIPTION: "PRs with the hold or needs-qa labels cannot be merged"
|
||||
|
||||
17
.github/workflows/pr-labeler.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: "Pull Request Labeler"
|
||||
|
||||
on:
|
||||
pull_request_target: {}
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
name: "Pull Request Labeler"
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
|
||||
with:
|
||||
sync-labels: true
|
||||
114
.github/workflows/release.yml
vendored
@@ -1,5 +1,6 @@
|
||||
---
|
||||
name: Release
|
||||
run-name: Release ${{ inputs.release_type }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -13,6 +14,11 @@ on:
|
||||
- Initial Release
|
||||
- Redeploy
|
||||
- Dry Run
|
||||
fdroid_publish:
|
||||
description: 'Publish to f-droid store'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -32,84 +38,118 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Retrieve Mobile release version
|
||||
id: retrieve-mobile-version
|
||||
run: |
|
||||
ver=$(sed -E -n '/^<manifest/s/^.*[ ]android:versionName="([^"]+)".*$/\1/p' \
|
||||
./src/Android/Properties/AndroidManifest.xml | tr -d '"')
|
||||
echo "::set-output name=mobile_version::${ver}"
|
||||
shell: bash
|
||||
|
||||
- name: Check to make sure Mobile release version has been bumped
|
||||
if: ${{ github.event.inputs.release_type == 'Initial Release' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
latest_ver=$(hub release -L 1 -f '%T')
|
||||
latest_ver=${latest_ver:1}
|
||||
echo "Latest version: $latest_ver"
|
||||
ver=${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
||||
echo "Version: $ver"
|
||||
if [ "$latest_ver" = "$ver" ]; then
|
||||
echo "Version has not been bumped!"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
- name: Check Release Version
|
||||
id: version
|
||||
uses: bitwarden/gh-actions/release-version-check@main
|
||||
with:
|
||||
release-type: ${{ github.event.inputs.release_type }}
|
||||
project-type: xamarin
|
||||
file: src/Android/Properties/AndroidManifest.xml
|
||||
|
||||
- name: Get branch name
|
||||
id: branch
|
||||
run: |
|
||||
BRANCH_NAME=$(basename ${{ github.ref }})
|
||||
echo "::set-output name=branch-name::$BRANCH_NAME"
|
||||
echo "branch-name=$BRANCH_NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create GitHub deployment
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: chrnorm/deployment-action@d42cde7132fcec920de534fffc3be83794335c00 # v2.0.5
|
||||
id: deployment
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
initial-status: 'in_progress'
|
||||
environment: 'production'
|
||||
description: 'Deployment ${{ steps.version.outputs.version }} from branch ${{ steps.branch.outputs.branch-name }}'
|
||||
task: release
|
||||
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ steps.branch.outputs.branch-name }}
|
||||
|
||||
- name: Dry Run - Download all artifacts
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: main
|
||||
|
||||
- name: Prep Bitwarden iOS release asset
|
||||
run: zip -r Bitwarden\ iOS.zip Bitwarden\ iOS
|
||||
|
||||
- name: Create release
|
||||
if: github.event.inputs.release_type != 'Dry Run'
|
||||
uses: ncipollo/release-action@40bb172bd05f266cf9ba4ff965cb61e9ee5f6d01 # v1.9.0
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
|
||||
with:
|
||||
artifacts: "./com.x8bit.bitwarden.aab/com.x8bit.bitwarden.aab,
|
||||
./com.x8bit.bitwarden.apk/com.x8bit.bitwarden.apk,
|
||||
./com.x8bit.bitwarden-fdroid.apk/com.x8bit.bitwarden-fdroid.apk,
|
||||
./Bitwarden iOS.zip"
|
||||
./Bitwarden iOS.zip,
|
||||
./bw-android-apk-sha256.txt/bw-android-apk-sha256.txt,
|
||||
./bw-fdroid-apk-sha256.txt/bw-fdroid-apk-sha256.txt"
|
||||
commit: ${{ github.sha }}
|
||||
tag: v${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
||||
name: Version ${{ steps.retrieve-mobile-version.outputs.mobile_version }}
|
||||
tag: v${{ steps.version.outputs.version }}
|
||||
name: Version ${{ steps.version.outputs.version }}
|
||||
body: "<insert release notes here>"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
draft: true
|
||||
|
||||
- name: Update deployment status to Success
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' && success() }}
|
||||
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
state: 'success'
|
||||
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
||||
|
||||
- name: Update deployment status to Failure
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' && failure() }}
|
||||
uses: chrnorm/deployment-status@2afb7d27101260f4a764219439564d954d10b5b0 # v2.0.1
|
||||
with:
|
||||
token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
state: 'failure'
|
||||
deployment-id: ${{ steps.deployment.outputs.deployment_id }}
|
||||
|
||||
|
||||
f-droid:
|
||||
name: F-Droid Release
|
||||
runs-on: ubuntu-20.04
|
||||
needs: release
|
||||
if: inputs.fdroid_publish
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.4.0
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Download F-Droid .apk artifact
|
||||
uses: dawidd6/action-download-artifact@575b1e4167df67acf7e692af784566618b23c71e # v2.17.10
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: ${{ needs.release.outputs.branch-name }}
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@1f8c6b94b26d0feae1e387ca63ccbdc44d27b561 # v2.5.1
|
||||
- name: Dry Run - Download F-Droid .apk artifact
|
||||
if: ${{ github.event.inputs.release_type == 'Dry Run' }}
|
||||
uses: dawidd6/action-download-artifact@246dbf436b23d7c49e21a7ab8204ca9ecd1fe615 # v2.27.0
|
||||
with:
|
||||
node-version: '10.x'
|
||||
workflow: build.yml
|
||||
workflow_conclusion: success
|
||||
branch: main
|
||||
name: com.x8bit.bitwarden-fdroid.apk
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
|
||||
with:
|
||||
node-version: '16.x'
|
||||
|
||||
- name: Set up F-Droid server
|
||||
run: |
|
||||
@@ -171,5 +211,5 @@ jobs:
|
||||
cd $GITHUB_WORKSPACE
|
||||
|
||||
- name: Deploy to gh-pages
|
||||
if: github.event.inputs.release_type != 'Dry Run'
|
||||
if: ${{ github.event.inputs.release_type != 'Dry Run' }}
|
||||
run: npm run deploy
|
||||
|
||||
30
.github/workflows/stale-bot.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
name: 'Close stale issues and PRs'
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule: # Run once a day at 5.23am (arbitrary but should avoid peak loads on the hour)
|
||||
- cron: '23 5 * * *'
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
name: 'Check for stale issues and PRs'
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: 'Run stale action'
|
||||
uses: actions/stale@f7176fd3007623b69d27091f9b9d4ab7995f0a06 # v5.2.1
|
||||
with:
|
||||
stale-issue-label: 'needs-reply'
|
||||
stale-pr-label: 'needs-changes'
|
||||
days-before-stale: -1 # Do not apply the stale labels automatically, this is a manual process
|
||||
days-before-issue-close: 14 # Close issue if no further activity after X days
|
||||
days-before-pr-close: 21 # Close PR if no further activity after X days
|
||||
close-issue-message: |
|
||||
We need more information before we can help you with your problem. As we haven’t heard from you recently, this issue will be closed.
|
||||
|
||||
If this happens again or continues to be an problem, please respond to this issue with the information we’ve requested and anything else relevant.
|
||||
close-pr-message: |
|
||||
We can’t merge your pull request until you make the changes we’ve requested. As we haven’t heard from you recently, this pull request will be closed.
|
||||
|
||||
If you’re still working on this, please respond here after you’ve made the changes we’ve requested and our team will re-open it for further review.
|
||||
|
||||
Please make sure to resolve any conflicts with the main branch before requesting another review.
|
||||
40
.github/workflows/version-auto-bump.yml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
name: Version Auto Bump
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- v**
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
name: "Setup"
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
version_number: ${{ steps.version.outputs.new-version }}
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
|
||||
|
||||
- name: Calculate bumped version
|
||||
id: version
|
||||
env:
|
||||
RELEASE_TAG: ${{ github.ref }}
|
||||
run: |
|
||||
CURR_MAJOR=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\1/')
|
||||
CURR_PATCH=$(echo $RELEASE_TAG | sed -r 's/refs\/tags\/v([0-9]{4}\.[0-9]{1,2})\.([0-9]{1,2})/\2/')
|
||||
echo "Current Major: $CURR_MAJOR"
|
||||
echo "Current Patch: $CURR_PATCH"
|
||||
|
||||
NEW_PATCH=$((CURR_PATCH+1))
|
||||
NEW_VER=$CURR_MAJOR.$NEW_PATCH
|
||||
echo "New Version: $NEW_VER"
|
||||
echo "new-version=$NEW_VER" >> $GITHUB_OUTPUT
|
||||
|
||||
trigger_version_bump:
|
||||
name: Bump version to ${{ needs.setup.outputs.version_number }}
|
||||
needs: setup
|
||||
uses: ./.github/workflows/version-bump.yml
|
||||
with:
|
||||
version_number: ${{ needs.setup.outputs.version_number }}
|
||||
secrets: inherit
|
||||
183
.github/workflows/version-bump.yml
vendored
@@ -1,79 +1,150 @@
|
||||
---
|
||||
name: Version Bump
|
||||
run-name: Version Bump - v${{ inputs.version_number }}
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_number:
|
||||
description: "New Version"
|
||||
description: "New version (example: '2024.1.0')"
|
||||
required: true
|
||||
cut_rc_branch:
|
||||
description: "Cut RC branch?"
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
bump_version:
|
||||
name: "Create version_bump_${{ github.event.inputs.version_number }} branch"
|
||||
runs-on: ubuntu-20.04
|
||||
name: "Bump Version to v${{ inputs.version_number }}"
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Login to Azure - CI Subscription
|
||||
uses: Azure/login@92a5484dfaf04ca78a94597f4f19fea633851fa2 # v1.4.7
|
||||
with:
|
||||
creds: ${{ secrets.AZURE_KV_CI_SERVICE_PRINCIPAL }}
|
||||
|
||||
- name: Retrieve secrets
|
||||
id: retrieve-secrets
|
||||
uses: bitwarden/gh-actions/get-keyvault-secrets@main
|
||||
with:
|
||||
keyvault: "bitwarden-ci"
|
||||
secrets: "github-gpg-private-key,
|
||||
github-gpg-private-key-passphrase,
|
||||
github-pat-bitwarden-devops-bot-repo-scope"
|
||||
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
|
||||
with:
|
||||
ref: main
|
||||
repository: bitwarden/mobile
|
||||
|
||||
- name: Import GPG key
|
||||
uses: crazy-max/ghaction-import-gpg@d6f3f49f3345e29369fe57596a3ca8f94c4d2ca7 # v5.4.0
|
||||
with:
|
||||
gpg_private_key: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key }}
|
||||
passphrase: ${{ steps.retrieve-secrets.outputs.github-gpg-private-key-passphrase }}
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: Create Version Branch
|
||||
id: create-branch
|
||||
run: |
|
||||
git switch -c version_bump_${{ github.event.inputs.version_number }}
|
||||
git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
NAME=version_bump_${{ github.ref_name }}_${{ inputs.version_number }}
|
||||
git switch -c $NAME
|
||||
echo "name=$NAME" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Checkout Version Branch
|
||||
uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579
|
||||
with:
|
||||
ref: version_bump_${{ github.event.inputs.version_number }}
|
||||
- name: Install xmllint
|
||||
run: sudo apt install -y libxml2-utils
|
||||
|
||||
- name: Verify input version
|
||||
env:
|
||||
NEW_VERSION: ${{ inputs.version_number }}
|
||||
run: |
|
||||
CURRENT_VERSION=$(xmllint --xpath '
|
||||
string(/manifest/@*[local-name()="versionName"
|
||||
and namespace-uri()="http://schemas.android.com/apk/res/android"])
|
||||
' src/Android/Properties/AndroidManifest.xml)
|
||||
|
||||
# Error if version has not changed.
|
||||
if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
|
||||
echo "Version has not changed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if version is newer.
|
||||
printf '%s\n' "${CURRENT_VERSION}" "${NEW_VERSION}" | sort -C -V
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Version check successful."
|
||||
else
|
||||
echo "Version check failed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Bump Version - Android XML
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/Android/Properties/AndroidManifest.xml"
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/Android/Properties/AndroidManifest.xml"
|
||||
|
||||
- name: Bump Version - iOS.Autofill
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/iOS.Autofill/Info.plist"
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.Autofill/Info.plist"
|
||||
|
||||
- name: Bump Version - iOS.Extension
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/iOS.Extension/Info.plist"
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.Extension/Info.plist"
|
||||
|
||||
- name: Bump Version - iOS.ShareExtension
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/iOS.ShareExtension/Info.plist"
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS.ShareExtension/Info.plist"
|
||||
|
||||
- name: Bump Version - iOS
|
||||
uses: bitwarden/gh-actions/version-bump@03ad9a873c39cdc95dd8d77dbbda67f84db43945
|
||||
uses: bitwarden/gh-actions/version-bump@main
|
||||
with:
|
||||
version: ${{ github.event.inputs.version_number }}
|
||||
file_path: "./src/iOS/Info.plist"
|
||||
version: ${{ inputs.version_number }}
|
||||
file_path: "src/iOS/Info.plist"
|
||||
|
||||
- name: Setup git
|
||||
run: |
|
||||
git config --local user.email "106330231+bitwarden-devops-bot@users.noreply.github.com"
|
||||
git config --local user.name "bitwarden-devops-bot"
|
||||
|
||||
- name: Check if version changed
|
||||
id: version-changed
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "changes_to_commit=TRUE" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "changes_to_commit=FALSE" >> $GITHUB_OUTPUT
|
||||
echo "No changes to commit!";
|
||||
fi
|
||||
|
||||
- name: Commit files
|
||||
run: |
|
||||
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git config --local user.name "github-actions[bot]"
|
||||
git commit -m "Bumped version to ${{ github.event.inputs.version_number }}" -a
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
run: git commit -m "Bumped version to ${{ inputs.version_number }}" -a
|
||||
|
||||
- name: Push changes
|
||||
run: git push -u origin version_bump_${{ github.event.inputs.version_number }}
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
env:
|
||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||
run: git push -u origin $PR_BRANCH
|
||||
|
||||
- name: Create Version PR
|
||||
if: ${{ steps.version-changed.outputs.changes_to_commit == 'TRUE' }}
|
||||
id: create-pr
|
||||
env:
|
||||
PR_BRANCH: "version_bump_${{ github.event.inputs.version_number }}"
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
BASE_BRANCH: master
|
||||
TITLE: "Bump version to ${{ github.event.inputs.version_number }}"
|
||||
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
PR_BRANCH: ${{ steps.create-branch.outputs.name }}
|
||||
TITLE: "Bump version to ${{ inputs.version_number }}"
|
||||
run: |
|
||||
gh pr create --title "$TITLE" \
|
||||
--base "$BASE" \
|
||||
PR_URL=$(gh pr create --title "$TITLE" \
|
||||
--base "main" \
|
||||
--head "$PR_BRANCH" \
|
||||
--label "version update" \
|
||||
--label "automated pr" \
|
||||
@@ -86,4 +157,42 @@ jobs:
|
||||
- [X] Other
|
||||
|
||||
## Objective
|
||||
Automated version bump to ${{ github.event.inputs.version_number }}"
|
||||
Automated version bump to ${{ inputs.version_number }}")
|
||||
echo "pr_number=${PR_URL##*/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Approve PR
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
run: gh pr review $PR_NUMBER --approve
|
||||
|
||||
- name: Merge PR
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.retrieve-secrets.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
|
||||
PR_NUMBER: ${{ steps.create-pr.outputs.pr_number }}
|
||||
run: gh pr merge $PR_NUMBER --squash --auto --delete-branch
|
||||
|
||||
cut_rc:
|
||||
name: Cut RC branch
|
||||
needs: bump_version
|
||||
if: ${{ inputs.cut_rc_branch == true }}
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout Branch
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
ref: main
|
||||
|
||||
- name: Check if RC branch exists
|
||||
run: |
|
||||
remote_rc_branch_check=$(git ls-remote --heads origin rc | wc -l)
|
||||
if [[ "${remote_rc_branch_check}" -gt 0 ]]; then
|
||||
echo "Remote RC branch exists."
|
||||
echo "Please delete current RC branch before running again."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Cut RC branch
|
||||
run: |
|
||||
git switch --quiet --create rc
|
||||
git push --quiet --set-upstream origin rc
|
||||
|
||||
2
.github/workflows/workflow-linter.yml
vendored
@@ -8,4 +8,4 @@ on:
|
||||
|
||||
jobs:
|
||||
call-workflow:
|
||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@master
|
||||
uses: bitwarden/gh-actions/.github/workflows/workflow-linter.yml@main
|
||||
|
||||
129
.gitignore
vendored
@@ -30,6 +30,7 @@ Components/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
!src/lib/x86/
|
||||
build/
|
||||
bld/
|
||||
[Bb]in/
|
||||
@@ -208,4 +209,130 @@ FakesAssemblies/
|
||||
# Other
|
||||
project.lock.json
|
||||
.DS_Store
|
||||
src/App/Css
|
||||
src/App/Css
|
||||
tools
|
||||
|
||||
# Created by https://www.toptal.com/developers/gitignore/api/swift,objective-c
|
||||
# Edit at https://www.toptal.com/developers/gitignore?templates=swift,objective-c
|
||||
|
||||
### Objective-C ###
|
||||
# Xcode
|
||||
#
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
## User settings
|
||||
xcuserdata/
|
||||
|
||||
## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
|
||||
*.xcscmblueprint
|
||||
*.xccheckout
|
||||
|
||||
## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
|
||||
build/
|
||||
DerivedData/
|
||||
*.moved-aside
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
|
||||
## Obj-C/Swift specific
|
||||
*.hmap
|
||||
|
||||
## App packaging
|
||||
*.ipa
|
||||
*.dSYM.zip
|
||||
*.dSYM
|
||||
|
||||
# CocoaPods
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
# Pods/
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
Carthage/Build/
|
||||
|
||||
# fastlane
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots/**/*.png
|
||||
fastlane/test_output
|
||||
|
||||
# Code Injection
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
iOSInjectionProject/
|
||||
|
||||
### Objective-C Patch ###
|
||||
|
||||
### Swift ###
|
||||
# Xcode
|
||||
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Playgrounds
|
||||
timeline.xctimeline
|
||||
playground.xcworkspace
|
||||
|
||||
# Swift Package Manager
|
||||
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
|
||||
# Packages/
|
||||
# Package.pins
|
||||
# Package.resolved
|
||||
# *.xcodeproj
|
||||
# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
|
||||
# hence it is not needed unless you have added a package configuration file to your project
|
||||
# .swiftpm
|
||||
|
||||
.build/
|
||||
|
||||
# CocoaPods
|
||||
# We recommend against adding the Pods directory to your .gitignore. However
|
||||
# you should judge for yourself, the pros and cons are mentioned at:
|
||||
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
||||
# Pods/
|
||||
# Add this line if you want to avoid checking in source code from the Xcode workspace
|
||||
# *.xcworkspace
|
||||
|
||||
# Carthage
|
||||
# Add this line if you want to avoid checking in source code from Carthage dependencies.
|
||||
# Carthage/Checkouts
|
||||
|
||||
|
||||
# Accio dependency management
|
||||
Dependencies/
|
||||
.accio/
|
||||
|
||||
# fastlane
|
||||
# It is recommended to not store the screenshots in the git repo.
|
||||
# Instead, use fastlane to re-generate the screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/#source-control
|
||||
|
||||
|
||||
# Code Injection
|
||||
# After new code Injection tools there's a generated folder /iOSInjectionProject
|
||||
# https://github.com/johnno1962/injectionforxcode
|
||||
|
||||
|
||||
# End of https://www.toptal.com/developers/gitignore/api/swift,objective-c
|
||||
|
||||
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "lib/MessagePack"]
|
||||
path = lib/MessagePack
|
||||
url = https://github.com/bitwarden/MessagePack.git
|
||||
@@ -1,40 +1,3 @@
|
||||
# How to Contribute
|
||||
|
||||
Contributions of all kinds are welcome!
|
||||
|
||||
Please visit our [Community Forums](https://community.bitwarden.com/) for general community discussion and the development roadmap.
|
||||
|
||||
Here is how you can get involved:
|
||||
|
||||
* **Request a new feature:** Go to the [Feature Requests category](https://community.bitwarden.com/c/feature-requests/) of the Community Forums. Please search existing feature requests before making a new one
|
||||
|
||||
* **Write code for a new feature:** Make a new post in the [Github Contributions category](https://community.bitwarden.com/c/github-contributions/) of the Community Forums. Include a description of your proposed contribution, screeshots, and links to any relevant feature requests. This helps get feedback from the community and Bitwarden team members before you start writing code
|
||||
|
||||
* **Report a bug or submit a bugfix:** Use Github issues and pull requests
|
||||
|
||||
* **Write documentation:** Submit a pull request to the [Bitwarden help repository](https://github.com/bitwarden/help)
|
||||
|
||||
* **Help other users:** Go to the [Ask the Bitwarden Community category](https://community.bitwarden.com/c/support/) on the Community Forums
|
||||
|
||||
* **Translate:** See the localization (i10n) section below
|
||||
|
||||
## Contributor Agreement
|
||||
|
||||
Please sign the [Contributor Agreement](https://cla-assistant.io/bitwarden/mobile) if you intend on contributing to any Github repository. Pull requests cannot be accepted and merged unless the author has signed the Contributor Agreement.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
* commit any pull requests against the `master` branch
|
||||
* include a link to your Community Forums post
|
||||
|
||||
# Localization (l10n)
|
||||
|
||||
[](https://crowdin.com/project/bitwarden-mobile)
|
||||
|
||||
We use a translation tool called [Crowdin](https://crowdin.com) to help manage our localization efforts across many different languages.
|
||||
|
||||
If you are interested in helping translate the Bitwarden mobile app into another language (or make a translation correction), please register an account at Crowdin and join our project here: https://crowdin.com/project/bitwarden-mobile
|
||||
|
||||
If the language that you are interested in translating is not already listed, create a new account on Crowdin, join the project, and contact the project owner (https://crowdin.com/profile/dwbit).
|
||||
|
||||
You can read Crowdin's getting started guide for translators here: https://support.crowdin.com/crowdin-intro/
|
||||
Our [Contributing Guidelines](https://contributing.bitwarden.com/contributing/) are located in our [Contributing Documentation](https://contributing.bitwarden.com/). The documentation also includes recommended tooling, code style tips, and lots of other great information to get you started.
|
||||
|
||||
36
README.md
@@ -4,7 +4,7 @@
|
||||
|
||||
# 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://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>
|
||||
<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 F-Droid" 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.
|
||||
|
||||
@@ -12,16 +12,7 @@ The Bitwarden mobile application is written in C# with Xamarin Android, Xamarin
|
||||
|
||||
# Build/Run
|
||||
|
||||
**Requirements**
|
||||
|
||||
- [Visual Studio](https://visualstudio.microsoft.com/)
|
||||
- [Xamarin](https://docs.microsoft.com/en-us/xamarin/get-started/installation/?pivots=windows)
|
||||
|
||||
**Run the app**
|
||||
|
||||
- Open the solution file in Visual Studio.
|
||||
- Restore the nuget packages.
|
||||
- Build and run the app.
|
||||
Please refer to the [Mobile section](https://contributing.bitwarden.com/getting-started/clients/mobile/) of the [Contributing Documentation](https://contributing.bitwarden.com/) for build instructions, recommended tooling, code style tips, and lots of other great information to get you started.
|
||||
|
||||
# We're Hiring!
|
||||
|
||||
@@ -29,27 +20,6 @@ Interested in contributing in a big way? Consider joining our team! We're hiring
|
||||
|
||||
# Contribute
|
||||
|
||||
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.
|
||||
Code contributions are welcome! Please commit any pull requests against the `master` branch. Learn more about how to contribute by reading the [Contributing Guidelines](https://contributing.bitwarden.com/contributing/). Check out the [Contributing Documentation](https://contributing.bitwarden.com/) for how to get started with your first contribution.
|
||||
|
||||
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.
|
||||
|
||||
### Dotnet-format
|
||||
|
||||
We recently migrated to using dotnet-format as code formatter. All previous branches will need to updated to avoid large merge conflicts using the following steps:
|
||||
|
||||
1. Check out your local Branch
|
||||
2. Run `git merge e0efcfbe45b2a27c73e9593bfd7a71fad2aa7a35`
|
||||
3. Resolve any merge conflicts, commit.
|
||||
4. Run `dotnet tool run dotnet-format`
|
||||
5. Commit
|
||||
6. Run `git merge -Xours 04539af2a66668b6e85476d5cf318c9150ec4357`
|
||||
7. Push
|
||||
|
||||
#### Git blame
|
||||
|
||||
We also recommend that you configure git to ignore the prettier revision using:
|
||||
|
||||
```bash
|
||||
git config blame.ignoreRevsFile .git-blame-ignore-revs
|
||||
```
|
||||
|
||||
11
appIcons/Android/beta-layered-excluded.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_41_29)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.0669V51.9453H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.211 57.5H70.1118V59H67.177C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.1118V51.9219H67.211V53.8027H69.895V55.291H67.211V57.5Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM59.7817 50.4336H57.1157V59H60.3208C60.9692 59 61.5278 58.9023 61.9966 58.707C62.4692 58.5078 62.8325 58.2227 63.0864 57.8516C63.3403 57.4805 63.4673 57.0352 63.4673 56.5156C63.4673 56.0664 63.397 55.707 63.2563 55.4375C63.1196 55.1641 62.936 54.957 62.7056 54.8164C62.4751 54.6719 62.2173 54.5703 61.9321 54.5117V54.4531C62.2134 54.4023 62.4517 54.293 62.647 54.125C62.8423 53.957 62.9907 53.7422 63.0923 53.4805C63.1978 53.2188 63.2505 52.9258 63.2505 52.6016C63.2505 51.7969 62.9575 51.2344 62.3716 50.9141C61.7856 50.5938 60.9224 50.4336 59.7817 50.4336ZM59.9868 53.8262H58.9321V51.9219H59.8872C60.4067 51.9219 60.7856 51.9941 61.0239 52.1387C61.2661 52.2793 61.3872 52.5137 61.3872 52.8418C61.3872 53.166 61.2856 53.4121 61.0825 53.5801C60.8794 53.7441 60.5142 53.8262 59.9868 53.8262ZM58.9321 57.5V55.2676H60.0571C60.4438 55.2676 60.7466 55.3125 60.9653 55.4023C61.188 55.4922 61.3462 55.6172 61.4399 55.7773C61.5337 55.9375 61.5806 56.123 61.5806 56.334C61.5806 56.6895 61.4731 56.9727 61.2583 57.1836C61.0435 57.3945 60.6626 57.5 60.1157 57.5H58.9321ZM65.1782 59H70.1118V57.5H66.9946V55.291H69.895V53.8027H66.9946V51.9219H70.1118V50.4336H65.1782V59ZM73.3931 59H75.2095V51.9453H77.5356V50.4336H71.0669V51.9453H73.3931V59ZM83.4771 56.9609L84.0981 59H86.0552L83.02 50.3984H80.7993L77.7759 59H79.7329L80.354 56.9609H83.4771ZM82.4224 53.4453L83.0435 55.4375H80.811L81.4263 53.4453C81.4536 53.3555 81.4985 53.2051 81.561 52.9941C81.6235 52.7832 81.688 52.5605 81.7544 52.3262C81.8247 52.0879 81.8794 51.8887 81.9185 51.7285C81.9575 51.8887 82.0083 52.0781 82.0708 52.2969C82.1372 52.5117 82.2017 52.7246 82.2642 52.9355C82.3306 53.1426 82.3833 53.3125 82.4224 53.4453Z" fill="#6795E8"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_41_29">
|
||||
<rect width="108" height="108" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
17
appIcons/Android/beta-layered.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_36_10)">
|
||||
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_10)"/>
|
||||
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
|
||||
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#6795E8"/>
|
||||
<path d="M57.116 50.4336H59.782C60.9226 50.4336 61.7859 50.5938 62.3718 50.9141C62.9578 51.2344 63.2507 51.7969 63.2507 52.6016C63.2507 52.9258 63.198 53.2188 63.0925 53.4805C62.991 53.7422 62.8425 53.957 62.6472 54.125C62.4519 54.293 62.2136 54.4023 61.9324 54.4531V54.5117C62.2175 54.5703 62.4753 54.6719 62.7058 54.8164C62.9363 54.957 63.1199 55.1641 63.2566 55.4375C63.3972 55.707 63.4675 56.0664 63.4675 56.5156C63.4675 57.0352 63.3406 57.4805 63.0867 57.8516C62.8328 58.2227 62.4695 58.5078 61.9968 58.707C61.5281 58.9023 60.9695 59 60.321 59H57.116V50.4336ZM58.9324 53.8262H59.9871C60.5144 53.8262 60.8796 53.7441 61.0828 53.5801C61.2859 53.4121 61.3875 53.166 61.3875 52.8418C61.3875 52.5137 61.2664 52.2793 61.0242 52.1387C60.7859 51.9941 60.407 51.9219 59.8875 51.9219H58.9324V53.8262ZM58.9324 55.2676V57.5H60.116C60.6628 57.5 61.0437 57.3945 61.2585 57.1836C61.4734 56.9727 61.5808 56.6895 61.5808 56.334C61.5808 56.123 61.5339 55.9375 61.4402 55.7773C61.3464 55.6172 61.1882 55.4922 60.9656 55.4023C60.7468 55.3125 60.4441 55.2676 60.0574 55.2676H58.9324ZM70.1121 59H65.1785V50.4336H70.1121V51.9219H66.9949V53.8027H69.8953V55.291H66.9949V57.5H70.1121V59ZM75.2097 59H73.3933V51.9453H71.0671V50.4336H77.5359V51.9453H75.2097V59ZM84.0984 59L83.4773 56.9609H80.3542L79.7332 59H77.7761L80.7996 50.3984H83.0203L86.0554 59H84.0984ZM83.0437 55.4375L82.4226 53.4453C82.3835 53.3125 82.3308 53.1426 82.2644 52.9355C82.2019 52.7246 82.1375 52.5117 82.071 52.2969C82.0085 52.0781 81.9578 51.8887 81.9187 51.7285C81.8796 51.8887 81.825 52.0879 81.7546 52.3262C81.6882 52.5605 81.6238 52.7832 81.5613 52.9941C81.4988 53.2051 81.4539 53.3555 81.4265 53.4453L80.8113 55.4375H83.0437Z" fill="#212529"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_36_10" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.247059"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_36_10">
|
||||
<rect width="108" height="108" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
11
appIcons/Android/dev-layered-excluded.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_41_21)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V50.4336H71.647L72.0971 51.7604V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM67.177 59C66.4282 66.7468 53.5337 73.2875 53.5337 73.2875V38.7573H67.211V50.4336H70.9321V51.9219H67.8149V53.8027H70.7153V55.291H67.8149V57.5H70.9321V59H67.177Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM63.6665 57.0547C64.0376 56.4062 64.2231 55.5996 64.2231 54.6348C64.2231 53.7168 64.0415 52.9473 63.6782 52.3262C63.3149 51.7012 62.8032 51.2305 62.1431 50.9141C61.4829 50.5938 60.7036 50.4336 59.8052 50.4336H57.1157V59H59.5415C60.5259 59 61.3677 58.8379 62.0669 58.5137C62.7661 58.1855 63.2993 57.6992 63.6665 57.0547ZM62.0552 53.123C62.2427 53.5293 62.3364 54.0488 62.3364 54.6816C62.3364 55.6152 62.1196 56.3184 61.686 56.791C61.2563 57.2637 60.5981 57.5 59.7114 57.5H58.9321V51.9219H59.8989C60.4302 51.9219 60.8755 52.0195 61.2349 52.2148C61.5981 52.4102 61.8716 52.7129 62.0552 53.123ZM65.9985 59H70.9321V57.5H67.8149V55.291H70.7153V53.8027H67.8149V51.9219H70.9321V50.4336H65.9985V59ZM76.5337 59L79.4458 50.4336H77.6118L75.9888 55.5312C75.9614 55.6211 75.9165 55.7852 75.854 56.0234C75.7954 56.2578 75.7349 56.5059 75.6724 56.7676C75.6138 57.0293 75.5728 57.2461 75.5493 57.418C75.5259 57.2461 75.481 57.0293 75.4146 56.7676C75.3521 56.502 75.2896 56.252 75.2271 56.0176C75.1646 55.7793 75.1196 55.6172 75.0923 55.5312L73.481 50.4336H71.647L74.5532 59H76.5337Z" fill="#2DA49D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_41_21">
|
||||
<rect width="108" height="108" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
17
appIcons/Android/dev-layered.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_36_2)">
|
||||
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_36_2)"/>
|
||||
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
|
||||
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#2DA49D"/>
|
||||
<path d="M64.2234 54.6348C64.2234 55.5996 64.0378 56.4062 63.6667 57.0547C63.2996 57.6992 62.7664 58.1855 62.0671 58.5137C61.3679 58.8379 60.5261 59 59.5417 59H57.116V50.4336H59.8054C60.7039 50.4336 61.4832 50.5938 62.1433 50.9141C62.8035 51.2305 63.3152 51.7012 63.6785 52.3262C64.0417 52.9473 64.2234 53.7168 64.2234 54.6348ZM62.3367 54.6816C62.3367 54.0488 62.2429 53.5293 62.0554 53.123C61.8718 52.7129 61.5984 52.4102 61.2351 52.2148C60.8757 52.0195 60.4304 51.9219 59.8992 51.9219H58.9324V57.5H59.7117C60.5984 57.5 61.2566 57.2637 61.6863 56.791C62.1199 56.3184 62.3367 55.6152 62.3367 54.6816ZM70.9324 59H65.9988V50.4336H70.9324V51.9219H67.8152V53.8027H70.7156V55.291H67.8152V57.5H70.9324V59ZM79.446 50.4336L76.5339 59H74.5535L71.6472 50.4336H73.4812L75.0925 55.5312C75.1199 55.6172 75.1648 55.7793 75.2273 56.0176C75.2898 56.252 75.3523 56.502 75.4148 56.7676C75.4812 57.0293 75.5261 57.2461 75.5496 57.418C75.573 57.2461 75.614 57.0293 75.6726 56.7676C75.7351 56.5059 75.7957 56.2578 75.8542 56.0234C75.9167 55.7852 75.9617 55.6211 75.989 55.5312L77.6121 50.4336H79.446Z" fill="#212529"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_36_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.247059"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_36_2">
|
||||
<rect width="108" height="108" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
11
appIcons/Android/qa-layered-excluded.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_41_13)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M70.214 34C70.7096 34 71.1457 34.1883 71.5124 34.555C71.8791 34.9217 72.0674 35.3479 72.0971 35.8534V52.9823L70.8325 49.3984H68.6118L67.211 53.3838V38.7573H53.5337V73.2875C53.5337 73.2875 67.211 66.3497 67.211 58.3019V58H67.5454L68.1665 55.9609H71.2896L71.9106 58H72.0971V58.0938C72.0971 59.749 71.7701 61.3942 71.1258 63.0295C70.4816 64.6549 69.6788 66.1019 68.7274 67.3706C67.766 68.6293 66.6262 69.8582 65.308 71.0575C63.98 72.2567 62.7609 73.2478 61.6409 74.0407C60.521 74.8237 59.3515 75.5769 58.1324 76.2806C56.9134 76.9843 56.0511 77.4699 55.5357 77.7177C55.0303 77.9655 54.614 78.1538 54.3068 78.2926C54.0788 78.4115 53.8211 78.471 53.5535 78.471C53.2859 78.471 53.0282 78.4115 52.8003 78.2926C52.5297 78.1791 52.1822 78.0118 51.7511 77.8042C51.6927 77.7761 51.6328 77.7473 51.5713 77.7177C51.0559 77.46 50.1937 76.9843 48.9746 76.2806C47.7555 75.5769 46.586 74.8336 45.4661 74.0407C44.3461 73.2478 43.1172 72.2567 41.799 71.0575C40.4709 69.8682 39.3311 68.6392 38.3797 67.3706C37.4183 66.1119 36.6155 64.6648 35.9713 63.0295C35.3271 61.4041 35 59.749 35 58.0938V35.8534C35 35.3479 35.1883 34.9217 35.555 34.555C35.9217 34.1883 36.3479 34 36.8534 34H70.214ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M58 46C56.3431 46 55 47.3431 55 49V60C55 61.6569 56.3431 63 58 63H107C108.657 63 110 61.6569 110 60V49C110 47.3431 108.657 46 107 46H58ZM64.6626 55.457C64.8149 54.9258 64.8911 54.3418 64.8911 53.7051C64.8911 52.8145 64.7446 52.0391 64.4517 51.3789C64.1626 50.7188 63.7173 50.207 63.1157 49.8438C62.5181 49.4805 61.7544 49.2988 60.8247 49.2988C59.8911 49.2988 59.1216 49.4805 58.5161 49.8438C57.9106 50.207 57.4614 50.7188 57.1685 51.3789C56.8794 52.0352 56.7349 52.8066 56.7349 53.6934C56.7349 54.3574 56.8169 54.9609 56.981 55.5039C57.145 56.0469 57.3931 56.5137 57.7251 56.9043C58.061 57.2949 58.4849 57.5957 58.9966 57.8066C59.5083 58.0137 60.1138 58.1172 60.813 58.1172H60.8774H60.9478L62.5181 60.0391H64.8442L62.7817 57.7363C63.2622 57.5176 63.6587 57.2148 63.9712 56.8281C64.2837 56.4414 64.5142 55.9844 64.6626 55.457ZM58.8618 55.252C58.7134 54.8184 58.6392 54.3027 58.6392 53.7051C58.6392 53.1035 58.7134 52.5879 58.8618 52.1582C59.0142 51.7246 59.2505 51.3926 59.5708 51.1621C59.895 50.9277 60.313 50.8105 60.8247 50.8105C61.5942 50.8105 62.147 51.0684 62.4829 51.584C62.8188 52.0996 62.9868 52.8066 62.9868 53.7051C62.9868 54.3027 62.9126 54.8184 62.7642 55.252C62.6196 55.6816 62.3872 56.0137 62.0669 56.248C61.7466 56.4785 61.3286 56.5938 60.813 56.5938C60.3052 56.5938 59.8911 56.4785 59.5708 56.248C59.2505 56.0137 59.0142 55.6816 58.8618 55.252ZM71.2896 55.9609L71.9106 58H73.8677L70.8325 49.3984H68.6118L65.5884 58H67.5454L68.1665 55.9609H71.2896ZM70.2349 52.4453L70.856 54.4375H68.6235L69.2388 52.4453C69.2661 52.3555 69.311 52.2051 69.3735 51.9941C69.436 51.7832 69.5005 51.5605 69.5669 51.3262C69.6372 51.0879 69.6919 50.8887 69.731 50.7285C69.77 50.8887 69.8208 51.0781 69.8833 51.2969C69.9497 51.5117 70.0142 51.7246 70.0767 51.9355C70.1431 52.1426 70.1958 52.3125 70.2349 52.4453Z" fill="#C32998"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_41_13">
|
||||
<rect width="108" height="108" rx="34" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
17
appIcons/Android/qa-layered.svg
Normal file
@@ -0,0 +1,17 @@
|
||||
<svg width="108" height="108" viewBox="0 0 108 108" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34_2)">
|
||||
<path d="M71.5717 34.6343L113 76.0625L89.709 119.077L40.6492 70.0168L53.5336 77.4501L70.8779 60.1057L71.5717 34.6343Z" fill="url(#paint0_linear_34_2)"/>
|
||||
<path d="M71.5124 34.555C71.1457 34.1883 70.7096 34 70.214 34H36.8534C36.3479 34 35.9217 34.1883 35.555 34.555C35.1883 34.9217 35 35.3479 35 35.8534V58.0938C35 59.749 35.3271 61.4041 35.9713 63.0295C36.6155 64.6648 37.4183 66.1119 38.3797 67.3706C39.3311 68.6392 40.4709 69.8682 41.799 71.0575C43.1172 72.2567 44.3461 73.2478 45.4661 74.0407C46.586 74.8336 47.7555 75.5769 48.9746 76.2806C50.1937 76.9843 51.0559 77.46 51.5713 77.7177C52.0867 77.9655 52.493 78.1637 52.8003 78.2926C53.0282 78.4115 53.2859 78.471 53.5535 78.471C53.8211 78.471 54.0788 78.4115 54.3068 78.2926C54.614 78.1538 55.0303 77.9655 55.5357 77.7177C56.0511 77.4699 56.9134 76.9843 58.1324 76.2806C59.3515 75.5769 60.521 74.8237 61.6409 74.0407C62.7609 73.2478 63.98 72.2567 65.308 71.0575C66.6262 69.8582 67.766 68.6293 68.7274 67.3706C69.6788 66.1019 70.4816 64.6549 71.1258 63.0295C71.7701 61.3942 72.0971 59.749 72.0971 58.0938V35.8534C72.0674 35.3479 71.8791 34.9217 71.5124 34.555ZM67.211 58.3019C67.211 66.3497 53.5337 73.2875 53.5337 73.2875V38.7573H67.211C67.211 38.7573 67.211 50.2542 67.211 58.3019Z" fill="white"/>
|
||||
<path d="M55 49C55 47.3431 56.3431 46 58 46H107C108.657 46 110 47.3431 110 49V60C110 61.6569 108.657 63 107 63H58C56.3431 63 55 61.6569 55 60V49Z" fill="#C32998"/>
|
||||
<path d="M64.8914 53.7051C64.8914 54.3418 64.8152 54.9258 64.6628 55.457C64.5144 55.9844 64.2839 56.4414 63.9714 56.8281C63.6589 57.2148 63.2625 57.5176 62.782 57.7363L64.8445 60.0391H62.5183L60.948 58.1172C60.9207 58.1172 60.8972 58.1172 60.8777 58.1172C60.8582 58.1172 60.8367 58.1172 60.8132 58.1172C60.114 58.1172 59.5085 58.0137 58.9968 57.8066C58.4851 57.5957 58.0613 57.2949 57.7253 56.9043C57.3933 56.5137 57.1453 56.0469 56.9812 55.5039C56.8171 54.9609 56.7351 54.3574 56.7351 53.6934C56.7351 52.8066 56.8796 52.0352 57.1687 51.3789C57.4617 50.7188 57.9109 50.207 58.5164 49.8438C59.1218 49.4805 59.8914 49.2988 60.825 49.2988C61.7546 49.2988 62.5183 49.4805 63.116 49.8438C63.7175 50.207 64.1628 50.7188 64.4519 51.3789C64.7449 52.0391 64.8914 52.8145 64.8914 53.7051ZM58.6394 53.7051C58.6394 54.3027 58.7136 54.8184 58.8621 55.252C59.0144 55.6816 59.2507 56.0137 59.571 56.248C59.8914 56.4785 60.3054 56.5938 60.8132 56.5938C61.3289 56.5938 61.7468 56.4785 62.0671 56.248C62.3875 56.0137 62.6199 55.6816 62.7644 55.252C62.9128 54.8184 62.9871 54.3027 62.9871 53.7051C62.9871 52.8066 62.8191 52.0996 62.4832 51.584C62.1472 51.0684 61.5945 50.8105 60.825 50.8105C60.3132 50.8105 59.8953 50.9277 59.571 51.1621C59.2507 51.3926 59.0144 51.7246 58.8621 52.1582C58.7136 52.5879 58.6394 53.1035 58.6394 53.7051ZM71.9109 58L71.2898 55.9609H68.1667L67.5457 58H65.5886L68.6121 49.3984H70.8328L73.8679 58H71.9109ZM70.8562 54.4375L70.2351 52.4453C70.196 52.3125 70.1433 52.1426 70.0769 51.9355C70.0144 51.7246 69.95 51.5117 69.8835 51.2969C69.821 51.0781 69.7703 50.8887 69.7312 50.7285C69.6921 50.8887 69.6375 51.0879 69.5671 51.3262C69.5007 51.5605 69.4363 51.7832 69.3738 51.9941C69.3113 52.2051 69.2664 52.3555 69.239 52.4453L68.6238 54.4375H70.8562Z" fill="white"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_34_2" x1="37.8512" y1="38.8122" x2="89.011" y2="89.972" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-opacity="0.247059"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_34_2">
|
||||
<rect width="108" height="108" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
BIN
appIcons/iOS/beta.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
appIcons/iOS/dev.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
appIcons/iOS/prod.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
appIcons/iOS/qa.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
136
appIcons/icongen.sh
Executable file
@@ -0,0 +1,136 @@
|
||||
#! /bin/sh
|
||||
|
||||
function print_example() {
|
||||
echo "Example"
|
||||
echo " icons ios ~/AppIcon.pdf ~/Icons/"
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
echo "Usage"
|
||||
echo " icons <ios|watch|complication|macos> in-file.pdf (out-dir)"
|
||||
}
|
||||
|
||||
function command_exists() {
|
||||
if type "$1" >/dev/null 2>&1; then
|
||||
return 1
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
if command_exists "sips" == 0 ; then
|
||||
echo "sips tool not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ] ; then
|
||||
print_usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
PLATFORM="$1"
|
||||
FILE="$2"
|
||||
if [ -z "$PLATFORM" ] || [ -z "$FILE" ] ; then
|
||||
echo "Error: missing arguments"
|
||||
echo ""
|
||||
print_usage
|
||||
echo ""
|
||||
print_example
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIR="$3"
|
||||
if [ -z "$DIR" ] ; then
|
||||
DIR=$(dirname $FILE)
|
||||
fi
|
||||
|
||||
# Create directory if needed
|
||||
mkdir -p "$DIR"
|
||||
|
||||
if [[ "$PLATFORM" == *"ios"* ]] ; then # iOS
|
||||
sips -s format png -Z '180' "${FILE}" --out "${DIR}"/Icon-180.png
|
||||
sips -s format png -Z '29' "${FILE}" --out "${DIR}"/Icon-29.png
|
||||
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/Icon-58.png
|
||||
sips -s format png -Z '120' "${FILE}" --out "${DIR}"/Icon-120.png
|
||||
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/Icon-87.png
|
||||
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Icon-40.png
|
||||
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Icon-80.png
|
||||
sips -s format png -Z '76' "${FILE}" --out "${DIR}"/Icon-76.png
|
||||
sips -s format png -Z '152' "${FILE}" --out "${DIR}"/Icon-152.png
|
||||
sips -s format png -Z '167' "${FILE}" --out "${DIR}"/Icon-167.png
|
||||
sips -s format png -Z '60' "${FILE}" --out "${DIR}"/Icon-60.png
|
||||
sips -s format png -Z '20' "${FILE}" --out "${DIR}"/Icon-20.png
|
||||
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/Icon-1024.png
|
||||
|
||||
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
|
||||
contents_json='{"images":[{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@2x.png","scale":"2x"},{"size":"20x20","idiom":"iphone","filename":"iPhoneNotification@3x.png","scale":"3x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@2x.png","scale":"2x"},{"size":"29x29","idiom":"iphone","filename":"iPhoneSettings@3x.png","scale":"3x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@2x.png","scale":"2x"},{"size":"40x40","idiom":"iphone","filename":"iPhoneSpotlight@3x.png","scale":"3x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@2x.png","scale":"2x"},{"size":"60x60","idiom":"iphone","filename":"iPhone@3x.png","scale":"3x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification.png","scale":"1x"},{"size":"20x20","idiom":"ipad","filename":"iPadNotification@2x.png","scale":"2x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings.png","scale":"1x"},{"size":"29x29","idiom":"ipad","filename":"iPadSettings@2x.png","scale":"2x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight.png","scale":"1x"},{"size":"40x40","idiom":"ipad","filename":"iPadSpotlight@2x.png","scale":"2x"},{"size":"76x76","idiom":"ipad","filename":"iPad.png","scale":"1x"},{"size":"76x76","idiom":"ipad","filename":"iPad@2x.png","scale":"2x"},{"size":"83.5x83.5","idiom":"ipad","filename":"iPadPro@2x.png","scale":"2x"},{"size":"1024x1024","idiom":"ios-marketing","filename":"AppStoreMarketing.png","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
|
||||
echo $contents_json > "${DIR}"/Contents.json
|
||||
fi
|
||||
|
||||
if [[ "$PLATFORM" == *"watch"* ]] ; then # Apple Watch
|
||||
sips -s format png -Z '48' "${FILE}" --out "${DIR}"/Watch38mmNotificationCenter.png
|
||||
sips -s format png -Z '55' "${FILE}" --out "${DIR}"/Watch42mmNotificationCenter.png
|
||||
sips -s format png -Z '66' "${FILE}" --out "${DIR}"/Watch66NotificationCenter.png
|
||||
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/WatchCompanionSettings@2x.png
|
||||
sips -s format png -Z '87' "${FILE}" --out "${DIR}"/WatchCompanionSettings@3x.png
|
||||
sips -s format png -Z '80' "${FILE}" --out "${DIR}"/Watch38MM42MMHomeScreen.png
|
||||
sips -s format png -Z '88' "${FILE}" --out "${DIR}"/Watch40MMHomeScreen.png
|
||||
sips -s format png -Z '92' "${FILE}" --out "${DIR}"/Watch41MMHomeScreen.png
|
||||
sips -s format png -Z '100' "${FILE}" --out "${DIR}"/Watch44MMHomeScreen.png
|
||||
sips -s format png -Z '102' "${FILE}" --out "${DIR}"/Watch45MMHomeScreen.png
|
||||
sips -s format png -Z '108' "${FILE}" --out "${DIR}"/Watch49MMHomeScreen.png
|
||||
sips -s format png -Z '172' "${FILE}" --out "${DIR}"/Watch38MMShortLook.png
|
||||
sips -s format png -Z '196' "${FILE}" --out "${DIR}"/Watch40MM42MMShortLook.png
|
||||
sips -s format png -Z '216' "${FILE}" --out "${DIR}"/Watch44MMShortLook.png
|
||||
sips -s format png -Z '234' "${FILE}" --out "${DIR}"/Watch234ShortLook.png
|
||||
sips -s format png -Z '258' "${FILE}" --out "${DIR}"/Watch258ShortLook.png
|
||||
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/WatchAppStore.png
|
||||
|
||||
# https://developer.apple.com/library/archive/documentation/Xcode/Reference/xcode_ref-Asset_Catalog_Format/AppIconType.html
|
||||
contents_json='{"images":[{"size":"24x24","idiom":"watch","scale":"2x","filename":"Watch38mmNotificationCenter.png","role":"notificationCenter","subtype":"38mm"},{"size":"27.5x27.5","idiom":"watch","scale":"2x","filename":"Watch42mmNotificationCenter.png","role":"notificationCenter","subtype":"42mm"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@2x.png","role":"companionSettings","scale":"2x"},{"size":"29x29","idiom":"watch","filename":"WatchCompanionSettings@3x.png","role":"companionSettings","scale":"3x"},{"size":"40x40","idiom":"watch","filename":"Watch38MM42MMHomeScreen.png","scale":"2x","role":"appLauncher","subtype":"38mm"},{"size":"44x44","idiom":"watch","scale":"2x","filename":"Watch40MMHomeScreen.png","role":"appLauncher","subtype":"40mm"},{"size":"50x50","idiom":"watch","scale":"2x","filename":"Watch44MMHomeScreen.png","role":"appLauncher","subtype":"44mm"},{"size":"86x86","idiom":"watch","scale":"2x","filename":"Watch38MMShortLook.png","role":"quickLook","subtype":"38mm"},{"size":"98x98","idiom":"watch","scale":"2x","filename":"Watch40MM42MMShortLook.png","role":"quickLook","subtype":"42mm"},{"size":"108x108","idiom":"watch","scale":"2x","filename":"Watch44MMShortLook.png","role":"quickLook","subtype":"44mm"},{"idiom":"watch-marketing","filename":"WatchAppStore.png","size":"1024x1024","scale":"1x"}],"info":{"version":1,"author":"xcode"}}'
|
||||
echo $contents_json > "${DIR}"/Contents.json
|
||||
fi
|
||||
|
||||
if [[ "$PLATFORM" == *"complication"* ]] ; then # Apple Watch
|
||||
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/Circular38mm2x.png
|
||||
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular40mm2x.png
|
||||
sips -s format png -Z '36' "${FILE}" --out "${DIR}"/Circular42mm2x.png
|
||||
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/Circular44mm2x.png
|
||||
sips -s format png -Z '182' "${FILE}" --out "${DIR}"/ExtraLarge38mm2x.png
|
||||
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge40mm2x.png
|
||||
sips -s format png -Z '203' "${FILE}" --out "${DIR}"/ExtraLarge42mm2x.png
|
||||
sips -s format png -Z '224' "${FILE}" --out "${DIR}"/ExtraLarge44mm2x.png
|
||||
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel40mm2x.png
|
||||
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicBezel42mm2x.png
|
||||
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicBezel44mm2x.png
|
||||
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular40mm2x.png
|
||||
sips -s format png -Z '84' "${FILE}" --out "${DIR}"/GraphicCircular42mm2x.png
|
||||
sips -s format png -Z '94' "${FILE}" --out "${DIR}"/GraphicCircular44mm2x.png
|
||||
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner40mm2x.png
|
||||
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicCorner42mm2x.png
|
||||
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicCorner44mm2x.png
|
||||
sips -s format png -Z '52' "${FILE}" --out "${DIR}"/GraphicModular38mm2x.png
|
||||
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular40mm2x.png
|
||||
sips -s format png -Z '58' "${FILE}" --out "${DIR}"/GraphicModular42mm2x.png
|
||||
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/GraphicModular44mm2x.png
|
||||
sips -s format png -Z '40' "${FILE}" --out "${DIR}"/GraphicUtilitarian38mm2x.png
|
||||
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian40mm2x.png
|
||||
sips -s format png -Z '44' "${FILE}" --out "${DIR}"/GraphicUtilitarian42mm2x.png
|
||||
sips -s format png -Z '50' "${FILE}" --out "${DIR}"/GraphicUtilitarian44mm2x.png
|
||||
sips -s format png -Z '206' "${FILE}" --out "${DIR}"/GraphicExtraLarge38mm2x.png
|
||||
sips -s format png -Z '264' "${FILE}" --out "${DIR}"/GraphicExtraLarge44mm2x.png
|
||||
echo "NOTE: Graphic Extra Large is not generated since that is not rectangular"
|
||||
fi
|
||||
|
||||
if [[ "$PLATFORM" == *"macos"* ]] ; then # macOS
|
||||
sips -s format png -Z '1024' "${FILE}" --out "${DIR}"/icon_512x512@2x.png
|
||||
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_512x512.png
|
||||
sips -s format png -Z '512' "${FILE}" --out "${DIR}"/icon_256x256@2x.png
|
||||
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_256x256.png
|
||||
sips -s format png -Z '256' "${FILE}" --out "${DIR}"/icon_128x128@2x.png
|
||||
sips -s format png -Z '128' "${FILE}" --out "${DIR}"/icon_128x128.png
|
||||
sips -s format png -Z '64' "${FILE}" --out "${DIR}"/icon_32x32@2x.png
|
||||
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_32x32.png
|
||||
sips -s format png -Z '32' "${FILE}" --out "${DIR}"/icon_16x16@2x.png
|
||||
sips -s format png -Z '16' "${FILE}" --out "${DIR}"/icon_16x16.png
|
||||
fi
|
||||
440
build.cake
Normal file
@@ -0,0 +1,440 @@
|
||||
#addin nuget:?package=Cake.FileHelpers&version=5.0.0
|
||||
#addin nuget:?package=Cake.AndroidAppManifest&version=1.1.2
|
||||
#addin nuget:?package=Cake.Plist&version=0.7.0
|
||||
#addin nuget:?package=Cake.Incubator&version=7.0.0
|
||||
#tool dotnet:?package=GitVersion.Tool&version=5.10.3
|
||||
using Path = System.IO.Path;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
var debugScript = Argument<bool>("debugScript", false);
|
||||
var target = Argument("target", "Default");
|
||||
var configuration = Argument("configuration", "Release");
|
||||
var variant = Argument("variant", "dev");
|
||||
|
||||
abstract record VariantConfig(
|
||||
string AppName,
|
||||
string AndroidPackageName,
|
||||
string iOSBundleId,
|
||||
string ApsEnvironment
|
||||
);
|
||||
|
||||
const string BASE_BUNDLE_ID_DROID = "com.x8bit.bitwarden";
|
||||
const string BASE_BUNDLE_ID_IOS = "com.8bit.bitwarden";
|
||||
|
||||
record Dev(): VariantConfig("Bitwarden Dev", $"{BASE_BUNDLE_ID_DROID}.dev", $"{BASE_BUNDLE_ID_IOS}.dev", "development");
|
||||
record QA(): VariantConfig("Bitwarden QA", $"{BASE_BUNDLE_ID_DROID}.qa", $"{BASE_BUNDLE_ID_IOS}.qa", "development");
|
||||
record Beta(): VariantConfig("Bitwarden Beta", $"{BASE_BUNDLE_ID_DROID}.beta", $"{BASE_BUNDLE_ID_IOS}.beta", "production");
|
||||
record Prod(): VariantConfig("Bitwarden", $"{BASE_BUNDLE_ID_DROID}", $"{BASE_BUNDLE_ID_IOS}", "production");
|
||||
|
||||
VariantConfig GetVariant() => variant.ToLower() switch{
|
||||
"qa" => new QA(),
|
||||
"beta" => new Beta(),
|
||||
"prod" => new Prod(),
|
||||
_ => new Dev()
|
||||
};
|
||||
|
||||
GitVersion _gitVersion; //will be set by GetGitInfo task
|
||||
var _slnPath = Path.Combine(""); //base path used to access files. If build.cake file is moved, just update this
|
||||
string _androidPackageName = string.Empty; //will be set by UpdateAndroidManifest task
|
||||
string _iOSVersionName = string.Empty; //will be set by UpdateiOSPlist task
|
||||
string CreateFeatureBranch(string prevVersionName, GitVersion git) => $"{prevVersionName}-{git.BranchName.Replace("/","-")}";
|
||||
string GetVersionName(string prevVersionName, VariantConfig buildVariant, GitVersion git) => buildVariant is Prod? prevVersionName : CreateFeatureBranch(prevVersionName, git);
|
||||
int CreateBuildNumber(int previousNumber) => ++previousNumber;
|
||||
|
||||
Task("GetGitInfo")
|
||||
.Does(()=> {
|
||||
_gitVersion = GitVersion(new GitVersionSettings());
|
||||
|
||||
if(debugScript)
|
||||
{
|
||||
Information($"GitVersion Dump:\n{_gitVersion.Dump()}");
|
||||
}
|
||||
|
||||
Information("Git data Load successfully.");
|
||||
});
|
||||
|
||||
#region Android
|
||||
Task("UpdateAndroidAppIcon")
|
||||
.Does(()=>{
|
||||
//TODO we'll implement variant icons later
|
||||
//manifest.ApplicationIcon = "@mipmap/ic_launcher";
|
||||
Information($"Updated Androix App Icon with success");
|
||||
});
|
||||
|
||||
|
||||
Task("UpdateAndroidManifest")
|
||||
.IsDependentOn("GetGitInfo")
|
||||
.Does(()=>
|
||||
{
|
||||
var buildVariant = GetVariant();
|
||||
var manifestPath = Path.Combine(_slnPath, "src", "Android", "Properties", "AndroidManifest.xml");
|
||||
|
||||
// Cake.AndroidAppManifest doesn't currently enable us to access nested items so, quick (not ideal) fix:
|
||||
var manifestText = FileReadText(manifestPath);
|
||||
manifestText = manifestText.Replace("com.x8bit.bitwarden.", buildVariant.AndroidPackageName + ".");
|
||||
manifestText = manifestText.Replace("android:label=\"Bitwarden\"", $"android:label=\"{buildVariant.AppName}\"");
|
||||
FileWriteText(manifestPath, manifestText);
|
||||
|
||||
var manifest = DeserializeAppManifest(manifestPath);
|
||||
|
||||
var prevVersionCode = manifest.VersionCode;
|
||||
var prevVersionName = manifest.VersionName;
|
||||
_androidPackageName = manifest.PackageName;
|
||||
|
||||
//manifest.VersionCode = CreateBuildNumber(prevVersionCode);
|
||||
manifest.VersionName = GetVersionName(prevVersionName, buildVariant, _gitVersion);
|
||||
manifest.PackageName = buildVariant.AndroidPackageName;
|
||||
manifest.ApplicationLabel = buildVariant.AppName;
|
||||
|
||||
//Information($"AndroidManigest.xml VersionCode from {prevVersionCode} to {manifest.VersionCode}");
|
||||
Information($"AndroidManigest.xml VersionName from {prevVersionName} to {manifest.VersionName}");
|
||||
Information($"AndroidManigest.xml PackageName from {_androidPackageName} to {buildVariant.AndroidPackageName}");
|
||||
Information($"AndroidManigest.xml ApplicationLabel to {buildVariant.AppName}");
|
||||
|
||||
SerializeAppManifest(manifestPath, manifest);
|
||||
|
||||
Information("AndroidManifest updated with success!");
|
||||
});
|
||||
|
||||
void ReplaceInFile(string filePath, string oldtext, string newtext)
|
||||
{
|
||||
var fileText = FileReadText(filePath);
|
||||
|
||||
if(string.IsNullOrEmpty(fileText) || !fileText.Contains(oldtext))
|
||||
{
|
||||
throw new Exception($"Couldn't find {filePath} or it didn't contain: {oldtext}");
|
||||
}
|
||||
|
||||
fileText = fileText.Replace(oldtext, newtext);
|
||||
|
||||
FileWriteText(filePath, fileText);
|
||||
Information($"{filePath} modified successfully.");
|
||||
}
|
||||
|
||||
Task("UpdateAndroidCodeFiles")
|
||||
.IsDependentOn("UpdateAndroidManifest")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
|
||||
//We're not using _androidPackageName here because the codefile is currently slightly different string than the one in AndroidManifest.xml
|
||||
var keyName = "com.8bit.bitwarden";
|
||||
var fixedPackageName = buildVariant.AndroidPackageName.Replace("x8bit", "8bit");
|
||||
var filePath = Path.Combine(_slnPath, "src", "Android", "Services", "BiometricService.cs");
|
||||
ReplaceInFile(filePath, keyName, fixedPackageName);
|
||||
|
||||
var packageFileList = new string[] {
|
||||
Path.Combine(_slnPath, "src", "Android", "MainActivity.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "MainApplication.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Constants.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Accessibility", "AccessibilityService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillHelpers.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Receivers", "ClearClipboardAlarmReceiver.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Receivers", "EventUploadReceiver.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Receivers", "PackageReplacedReceiver.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Receivers", "RestrictionsChangedReceiver.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Services", "DeviceActionService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Services", "FileService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Tiles", "AutofillTileService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Tiles", "GeneratorTileService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "Tiles", "MyVaultTileService.cs"),
|
||||
Path.Combine(_slnPath, "src", "Android", "google-services.json"),
|
||||
Path.Combine(_slnPath, "store", "google", "Publisher", "Program.cs"),
|
||||
};
|
||||
|
||||
foreach(string path in packageFileList)
|
||||
{
|
||||
ReplaceInFile(path, "com.x8bit.bitwarden", buildVariant.AndroidPackageName);
|
||||
}
|
||||
|
||||
var labelFileList = new string[] {
|
||||
Path.Combine(_slnPath, "src", "Android", "Autofill", "AutofillService.cs"),
|
||||
};
|
||||
|
||||
foreach(string path in labelFileList)
|
||||
{
|
||||
ReplaceInFile(path, "Bitwarden\"", $"{buildVariant.AppName}\"");
|
||||
}
|
||||
});
|
||||
#endregion Android
|
||||
|
||||
#region iOS
|
||||
enum iOSProjectType
|
||||
{
|
||||
Null,
|
||||
MainApp,
|
||||
Autofill,
|
||||
Extension,
|
||||
ShareExtension,
|
||||
WatchApp
|
||||
}
|
||||
|
||||
string GetiOSBundleId(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
|
||||
{
|
||||
iOSProjectType.Autofill => $"{buildVariant.iOSBundleId}.autofill",
|
||||
iOSProjectType.Extension => $"{buildVariant.iOSBundleId}.find-login-action-extension",
|
||||
iOSProjectType.ShareExtension => $"{buildVariant.iOSBundleId}.share-extension",
|
||||
iOSProjectType.WatchApp => $"{buildVariant.iOSBundleId}.watchkitapp",
|
||||
_ => buildVariant.iOSBundleId
|
||||
};
|
||||
|
||||
string GetiOSBundleName(VariantConfig buildVariant, iOSProjectType projectType) => projectType switch
|
||||
{
|
||||
iOSProjectType.Autofill => $"{buildVariant.AppName} Autofill",
|
||||
iOSProjectType.Extension => $"{buildVariant.AppName} Extension",
|
||||
iOSProjectType.ShareExtension => $"{buildVariant.AppName} Share Extension",
|
||||
_ => buildVariant.AppName
|
||||
};
|
||||
|
||||
private void UpdateiOSInfoPlist(string plistPath, VariantConfig buildVariant, GitVersion git, iOSProjectType projectType = iOSProjectType.MainApp)
|
||||
{
|
||||
var plistFile = File(plistPath);
|
||||
dynamic plist = DeserializePlist(plistFile);
|
||||
|
||||
var prevVersionName = plist["CFBundleShortVersionString"];
|
||||
var prevVersionString = plist["CFBundleVersion"];
|
||||
var prevVersion = int.Parse(plist["CFBundleVersion"]);
|
||||
var prevBundleId = plist["CFBundleIdentifier"];
|
||||
var prevBundleName = plist["CFBundleName"];
|
||||
//var newVersion = CreateBuildNumber(prevVersion).ToString();
|
||||
var newVersionName = GetVersionName(prevVersionName, buildVariant, git);
|
||||
var newBundleId = GetiOSBundleId(buildVariant, projectType);
|
||||
var newBundleName = GetiOSBundleName(buildVariant, projectType);
|
||||
|
||||
plist["CFBundleName"] = newBundleName;
|
||||
plist["CFBundleDisplayName"] = newBundleName;
|
||||
//plist["CFBundleVersion"] = newVersion;
|
||||
plist["CFBundleShortVersionString"] = newVersionName;
|
||||
plist["CFBundleIdentifier"] = newBundleId;
|
||||
|
||||
if(projectType == iOSProjectType.MainApp)
|
||||
{
|
||||
_iOSVersionName = newVersionName;
|
||||
plist["CFBundleURLTypes"][0]["CFBundleURLName"] = $"{buildVariant.iOSBundleId}.url";
|
||||
}
|
||||
|
||||
if(projectType == iOSProjectType.Extension)
|
||||
{
|
||||
var keyText = plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"];
|
||||
plist["NSExtension"]["NSExtensionAttributes"]["NSExtensionActivationRule"] = keyText.Replace("com.8bit.bitwarden", buildVariant.iOSBundleId);
|
||||
}
|
||||
|
||||
SerializePlist(plistFile, plist);
|
||||
|
||||
Information($"Changed app name from {prevBundleName} to {newBundleName}");
|
||||
//Information($"Changed Bundle Version from {prevVersion} to {newVersion}");
|
||||
Information($"Changed Bundle Short Version name from {prevVersionName} to {newVersionName}");
|
||||
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
|
||||
Information($"{plistPath} updated with success!");
|
||||
}
|
||||
|
||||
private void UpdateiOSEntitlementsPlist(string entitlementsPath, VariantConfig buildVariant)
|
||||
{
|
||||
var EntitlementlistFile = File(entitlementsPath);
|
||||
dynamic Entitlements = DeserializePlist(EntitlementlistFile);
|
||||
|
||||
Entitlements["aps-environment"] = buildVariant.ApsEnvironment;
|
||||
Entitlements["keychain-access-groups"] = new List<string>() { "$(AppIdentifierPrefix)" + buildVariant.iOSBundleId };
|
||||
Entitlements["com.apple.security.application-groups"] = new List<string>() { $"group.{buildVariant.iOSBundleId}" };;
|
||||
|
||||
Information($"Changed ApsEnvironment name to {buildVariant.ApsEnvironment}");
|
||||
Information($"Changed keychain-access-groups bundleID to {buildVariant.iOSBundleId}");
|
||||
|
||||
SerializePlist(EntitlementlistFile, Entitlements);
|
||||
|
||||
Information($"{entitlementsPath} updated with success!");
|
||||
}
|
||||
|
||||
private void UpdateWatchKitAppInfoPlist(string plistPath, VariantConfig buildVariant)
|
||||
{
|
||||
var plistFile = File(plistPath);
|
||||
dynamic plist = DeserializePlist(plistFile);
|
||||
|
||||
var prevBundleId = plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"];
|
||||
var newBundleId = GetiOSBundleId(buildVariant, iOSProjectType.WatchApp);
|
||||
|
||||
plist["NSExtension"]["NSExtensionAttributes"]["WKAppBundleIdentifier"] = newBundleId;
|
||||
|
||||
SerializePlist(plistFile, plist);
|
||||
|
||||
Information($"Changed Bundle Identifier from {prevBundleId} to {newBundleId}");
|
||||
Information($"{plistPath} updated with success!");
|
||||
}
|
||||
|
||||
private void UpdateWatchPbxproj(string pbxprojPath, string newVersion)
|
||||
{
|
||||
var fileText = FileReadText(pbxprojPath);
|
||||
if (string.IsNullOrEmpty(fileText))
|
||||
{
|
||||
throw new Exception($"Couldn't find {pbxprojPath}");
|
||||
}
|
||||
|
||||
const string pattern = @"MARKETING_VERSION = [^;]*;";
|
||||
|
||||
fileText = Regex.Replace(fileText, pattern, $"MARKETING_VERSION = {newVersion};");
|
||||
|
||||
FileWriteText(pbxprojPath, fileText);
|
||||
Information($"{pbxprojPath} modified successfully.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the target icons on the given appiconset target
|
||||
/// taking as source the icon in appIcons/iOS folder for the giving variant
|
||||
/// </summary>
|
||||
/// <param name="target">It can be <ios|watch|complication|macos></param>
|
||||
/// <param name="appiconsetTarget">Folder to copy the generated icons to</param>
|
||||
private void UpdateAppleIcons(string target, string appiconsetTarget)
|
||||
{
|
||||
Information($"Updating {target} App Icons");
|
||||
|
||||
var iconsTempDirPath = Path.Combine(_slnPath, "appIcons", "temp");
|
||||
CreateDirectory(iconsTempDirPath);
|
||||
|
||||
var arguments = new ProcessArgumentBuilder();
|
||||
arguments.Append(target);
|
||||
arguments.Append(Path.Combine(_slnPath, "appIcons", "iOS", $"{variant}.png"));
|
||||
arguments.Append(iconsTempDirPath);
|
||||
|
||||
using(var process = StartAndReturnProcess(Path.Combine(_slnPath, "appIcons", "icongen.sh"),
|
||||
new ProcessSettings { Arguments = arguments }))
|
||||
{
|
||||
process.WaitForExit();
|
||||
Information("Exit code: {0}", process.GetExitCode());
|
||||
}
|
||||
|
||||
var generatedIconsPath = Path.Combine(iconsTempDirPath, "*.png");
|
||||
CopyFiles(generatedIconsPath, appiconsetTarget);
|
||||
|
||||
DeleteDirectory(iconsTempDirPath, new DeleteDirectorySettings {
|
||||
Recursive = true,
|
||||
Force = true
|
||||
});
|
||||
|
||||
Information($"{target} App Icons have been updated");
|
||||
}
|
||||
|
||||
Task("UpdateiOSIcons")
|
||||
.Does(()=>{
|
||||
UpdateAppleIcons("ios", Path.Combine(_slnPath, "src", "iOS", "Resources", "Assets.xcassets", "AppIcons.appiconset"));
|
||||
UpdateAppleIcons("watch", Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit App", "Assets.xcassets", "AppIcon.appiconset"));
|
||||
// TODO: Update complication icons when they start working
|
||||
});
|
||||
|
||||
Task("UpdateiOSPlist")
|
||||
.IsDependentOn("GetGitInfo")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var infoPath = Path.Combine(_slnPath, "src", "iOS", "Info.plist");
|
||||
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS", "Entitlements.plist");
|
||||
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.MainApp);
|
||||
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||
});
|
||||
|
||||
Task("UpdateiOSAutofillPlist")
|
||||
.IsDependentOn("GetGitInfo")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var infoPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Info.plist");
|
||||
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Autofill", "Entitlements.plist");
|
||||
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Autofill);
|
||||
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||
});
|
||||
|
||||
Task("UpdateiOSExtensionPlist")
|
||||
.IsDependentOn("GetGitInfo")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var infoPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Info.plist");
|
||||
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.Extension", "Entitlements.plist");
|
||||
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.Extension);
|
||||
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||
});
|
||||
|
||||
Task("UpdateiOSShareExtensionPlist")
|
||||
.IsDependentOn("GetGitInfo")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var infoPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Info.plist");
|
||||
var entitlementsPath = Path.Combine(_slnPath, "src", "iOS.ShareExtension", "Entitlements.plist");
|
||||
UpdateiOSInfoPlist(infoPath, buildVariant, _gitVersion, iOSProjectType.ShareExtension);
|
||||
UpdateiOSEntitlementsPlist(entitlementsPath, buildVariant);
|
||||
});
|
||||
|
||||
Task("UpdateiOSCodeFiles")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var fileList = new string[] {
|
||||
Path.Combine(_slnPath, "src", "iOS.Core", "Utilities", "iOSCoreHelpers.cs"),
|
||||
Path.Combine(_slnPath, "src", "iOS.Core", "Constants.cs"),
|
||||
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj"),
|
||||
Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Helpers", "KeychainHelper.swift"),
|
||||
Path.Combine(".github", "resources", "export-options-ad-hoc.plist"),
|
||||
Path.Combine(".github", "resources", "export-options-app-store.plist")
|
||||
};
|
||||
|
||||
foreach(string path in fileList)
|
||||
{
|
||||
ReplaceInFile(path, "com.8bit.bitwarden", buildVariant.iOSBundleId);
|
||||
}
|
||||
});
|
||||
|
||||
Task("UpdateWatchProject")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.WithCriteria(() => !string.IsNullOrEmpty(_iOSVersionName))
|
||||
.Does(()=> {
|
||||
var watchProjectPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden.xcodeproj", "project.pbxproj");
|
||||
UpdateWatchPbxproj(watchProjectPath, _iOSVersionName);
|
||||
});
|
||||
|
||||
Task("UpdateWatchKitAppInfoPlist")
|
||||
.Does(()=> {
|
||||
var buildVariant = GetVariant();
|
||||
var infoPath = Path.Combine(_slnPath, "src", "watchOS", "bitwarden", "bitwarden WatchKit Extension", "Info.plist");
|
||||
UpdateWatchKitAppInfoPlist(infoPath, buildVariant);
|
||||
});
|
||||
|
||||
#endregion iOS
|
||||
|
||||
#region Main Tasks
|
||||
Task("Android")
|
||||
//.IsDependentOn("UpdateAndroidAppIcon")
|
||||
.IsDependentOn("UpdateAndroidManifest")
|
||||
.IsDependentOn("UpdateAndroidCodeFiles")
|
||||
.Does(()=>
|
||||
{
|
||||
Information("Android app updated");
|
||||
});
|
||||
|
||||
Task("iOS")
|
||||
.IsDependentOn("UpdateiOSIcons")
|
||||
.IsDependentOn("UpdateiOSPlist")
|
||||
.IsDependentOn("UpdateiOSAutofillPlist")
|
||||
.IsDependentOn("UpdateiOSExtensionPlist")
|
||||
.IsDependentOn("UpdateiOSShareExtensionPlist")
|
||||
.IsDependentOn("UpdateiOSCodeFiles")
|
||||
.IsDependentOn("UpdateWatchProject")
|
||||
.IsDependentOn("UpdateWatchKitAppInfoPlist")
|
||||
.Does(()=>
|
||||
{
|
||||
Information("iOS app updated");
|
||||
});
|
||||
|
||||
Task("Default")
|
||||
.Does(() => {
|
||||
var usage = @"Missing target.
|
||||
|
||||
Usage:
|
||||
dotnet cake build.cake --target (Android | iOS) --variant (dev | qa | beta | prod)
|
||||
|
||||
Options:
|
||||
--debugScript=<bool> Script debug mode.
|
||||
";
|
||||
Information(usage);
|
||||
});
|
||||
#endregion Main Tasks
|
||||
|
||||
RunTarget(target);
|
||||
12
crowdin.yml
@@ -38,3 +38,15 @@ files:
|
||||
pt-PT: pt-PT
|
||||
en-GB: en-GB
|
||||
en-IN: en-IN
|
||||
- source: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/Localizable.strings"
|
||||
dest: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization/en.lproj/%original_file_name%"
|
||||
translation: "/src/watchOS/bitwarden/bitwarden WatchKit Extension/Localization//%two_letters_code%.lproj/%original_file_name%"
|
||||
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
|
||||
en-IN: en-IN
|
||||
|
||||
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.400",
|
||||
"rollForward": "latestPatch",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
||||
1
lib/MessagePack
Submodule
BIN
lib/ios/libargon2.a
Normal file
@@ -33,6 +33,7 @@ namespace Bit.Droid.Accessibility
|
||||
// - Resources/xml/autofillservice.xml
|
||||
new Browser("alook.browser", "search_fragment_input_view"),
|
||||
new Browser("alook.browser.google", "search_fragment_input_view"),
|
||||
new Browser("app.vanadium.browser", "url_bar"),
|
||||
new Browser("com.amazon.cloud9", "url"),
|
||||
new Browser("com.android.browser", "url"),
|
||||
new Browser("com.android.chrome", "url_bar"),
|
||||
@@ -54,6 +55,7 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("com.google.android.apps.chrome", "url_bar"),
|
||||
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
|
||||
// Rem. for "com.google.android.captiveportallogin": URL displayed in ActionBar subtitle without viewId.
|
||||
new Browser("com.iode.firefox", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("com.jamal2367.styx", "search"),
|
||||
new Browser("com.kiwibrowser.browser", "url_bar"),
|
||||
new Browser("com.kiwibrowser.browser.dev", "url_bar"),
|
||||
@@ -65,13 +67,16 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("com.mmbox.xbrowser", "search_box"),
|
||||
new Browser("com.mycompany.app.soulbrowser", "edit_text"),
|
||||
new Browser("com.naver.whale", "url_bar"),
|
||||
new Browser("com.neeva.app", "full_url_text_view"),
|
||||
new Browser("com.opera.browser", "url_field"),
|
||||
new Browser("com.opera.browser.beta", "url_field"),
|
||||
new Browser("com.opera.gx", "addressbarEdit"),
|
||||
new Browser("com.opera.mini.native", "url_field"),
|
||||
new Browser("com.opera.mini.native.beta", "url_field"),
|
||||
new Browser("com.opera.touch", "addressbarEdit"),
|
||||
new Browser("com.qflair.browserq", "url"),
|
||||
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
|
||||
new Browser("com.rainsee.create", "search_box"),
|
||||
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.stoutner.privacybrowser.free", "url_edittext"),
|
||||
@@ -81,6 +86,9 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("com.vivaldi.browser.sopranos", "url_bar"),
|
||||
new Browser("com.yandex.browser", "bro_omnibar_address_title_text,bro_omnibox_collapsed_title",
|
||||
(s) => s.Split(new char[]{' ', ' '}).FirstOrDefault()), // 0 = Regular Space, 1 = No-break space (00A0)
|
||||
new Browser("com.yjllq.internet", "search_box"),
|
||||
new Browser("com.yjllq.kito", "search_box"),
|
||||
new Browser("com.yujian.ResideMenuDemo", "search_box"),
|
||||
new Browser("com.z28j.feel", "g2"),
|
||||
new Browser("idm.internet.download.manager", "search"),
|
||||
new Browser("idm.internet.download.manager.adm.lite", "search"),
|
||||
@@ -88,6 +96,7 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("mark.via", "am,an"),
|
||||
new Browser("mark.via.gp", "as"),
|
||||
new Browser("net.dezor.browser", "url_bar"),
|
||||
new Browser("net.slions.fulguris.full.download", "search"),
|
||||
new Browser("net.slions.fulguris.full.download.debug", "search"),
|
||||
new Browser("net.slions.fulguris.full.playstore", "search"),
|
||||
@@ -98,6 +107,7 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("org.bromite.chromium", "url_bar"),
|
||||
new Browser("org.chromium.chrome", "url_bar"),
|
||||
new Browser("org.codeaurora.swe.browser", "url_bar"),
|
||||
new Browser("org.cromite.cromite", "url_bar"),
|
||||
new Browser("org.gnu.icecat", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
|
||||
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
|
||||
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"), // [DEPRECATED ENTRY]
|
||||
@@ -127,6 +137,7 @@ namespace Bit.Droid.Accessibility
|
||||
new Browser("com.htc.sense.browser", "title"),
|
||||
new Browser("com.jerky.browser2", "enterUrl"),
|
||||
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
|
||||
new Browser("com.lemurbrowser.exts","url_bar"),
|
||||
new Browser("com.linkbubble.playstore", "url_text"),
|
||||
new Browser("com.mx.browser", "address_editor_with_progress"),
|
||||
new Browser("com.mx.browser.tablet", "address_editor_with_progress"),
|
||||
@@ -365,7 +376,7 @@ namespace Bit.Droid.Accessibility
|
||||
|
||||
public static string GetUri(AccessibilityNodeInfo root)
|
||||
{
|
||||
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
|
||||
var uri = string.Concat(Core.Constants.AndroidAppProtocol, root.PackageName);
|
||||
if (SupportedBrowsers.ContainsKey(root.PackageName))
|
||||
{
|
||||
var browser = SupportedBrowsers[root.PackageName];
|
||||
|
||||
@@ -15,7 +15,7 @@ using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Accessibility
|
||||
{
|
||||
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden")]
|
||||
[Service(Permission = Android.Manifest.Permission.BindAccessibilityService, Label = "Bitwarden", Exported = true)]
|
||||
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
|
||||
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
|
||||
[Register("com.x8bit.bitwarden.Accessibility.AccessibilityService")]
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
|
||||
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
|
||||
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
|
||||
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v13.0</TargetFrameworkVersion>
|
||||
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
@@ -75,24 +75,23 @@
|
||||
<Version>2.1.0.4</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Portable.BouncyCastle">
|
||||
<Version>1.8.10</Version>
|
||||
<Version>1.9.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.3.1.3" />
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.9" />
|
||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.11" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0.10" />
|
||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.2.5.2" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.8" />
|
||||
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.6.1.3" />
|
||||
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.18" />
|
||||
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0.21" />
|
||||
<PackageReference Include="Xamarin.AndroidX.Core" Version="1.10.1.2" />
|
||||
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.4.0.2" />
|
||||
<PackageReference Include="Xamarin.Essentials">
|
||||
<Version>1.7.2</Version>
|
||||
<Version>1.8.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Firebase.Messaging">
|
||||
<Version>122.0.0</Version>
|
||||
<Version>123.1.2.2</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.4.0.4" />
|
||||
<PackageReference Include="Xamarin.Google.Dagger" Version="2.37.0" />
|
||||
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.9.0.2" />
|
||||
<PackageReference Include="Xamarin.Google.Dagger" Version="2.46.1.2" />
|
||||
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
|
||||
<Version>117.0.1</Version>
|
||||
<Version>118.0.1.5</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
@@ -103,8 +102,10 @@
|
||||
<Compile Include="Accessibility\Browser.cs" />
|
||||
<Compile Include="Accessibility\NodeList.cs" />
|
||||
<Compile Include="Accessibility\KnownUsernameField.cs" />
|
||||
<Compile Include="Autofill\AutofillConstants.cs" />
|
||||
<Compile Include="Autofill\AutofillHelpers.cs" />
|
||||
<Compile Include="Autofill\AutofillService.cs" />
|
||||
<Compile Include="Autofill\AutofillExternalSelectionActivity.cs" />
|
||||
<Compile Include="Autofill\Field.cs" />
|
||||
<Compile Include="Autofill\FieldCollection.cs" />
|
||||
<Compile Include="Autofill\FilledItem.cs" />
|
||||
@@ -145,12 +146,20 @@
|
||||
<Compile Include="Tiles\GeneratorTileService.cs" />
|
||||
<Compile Include="Tiles\MyVaultTileService.cs" />
|
||||
<Compile Include="Utilities\AndroidHelpers.cs" />
|
||||
<Compile Include="Utilities\AppCenterHelper.cs" />
|
||||
<Compile Include="Utilities\ThemeHelpers.cs" />
|
||||
<Compile Include="WebAuthCallbackActivity.cs" />
|
||||
<Compile Include="Renderers\SelectableLabelRenderer.cs" />
|
||||
<Compile Include="Services\ClipboardService.cs" />
|
||||
<Compile Include="Utilities\IntentExtensions.cs" />
|
||||
<Compile Include="Renderers\CustomPageRenderer.cs" />
|
||||
<Compile Include="Effects\NoEmojiKeyboardEffect.cs" />
|
||||
<Compile Include="Receivers\NotificationDismissReceiver.cs" />
|
||||
<Compile Include="Services\FileService.cs" />
|
||||
<Compile Include="Services\AutofillHandler.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="Effects\RemoveFontPaddingEffect.cs" />
|
||||
<Compile Include="Services\WatchDeviceService.cs" />
|
||||
<Compile Include="Renderers\CustomLabelRenderer.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidAsset Include="Assets\bwi-font.ttf" />
|
||||
@@ -160,6 +169,10 @@
|
||||
<GoogleServicesJson Include="google-services.json" />
|
||||
<GoogleServicesJson Include="google-services.json.enc" />
|
||||
<None Include="fdroid-keystore.jks.enc" />
|
||||
<AndroidNativeLibrary Include="lib\arm64-v8a\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="lib\armeabi-v7a\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="lib\x86\libargon2.so" />
|
||||
<AndroidNativeLibrary Include="lib\x86_64\libargon2.so" />
|
||||
<None Include="Properties\AndroidManifest.xml" />
|
||||
<None Include="upload-keystore.jks.enc" />
|
||||
</ItemGroup>
|
||||
@@ -175,6 +188,7 @@
|
||||
<AndroidResource Include="Resources\drawable\cog_settings.xml" />
|
||||
<AndroidResource Include="Resources\drawable\icon.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_launcher_monochrome.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_warning.xml" />
|
||||
<AndroidResource Include="Resources\drawable\id.xml" />
|
||||
<AndroidResource Include="Resources\drawable\info.xml" />
|
||||
@@ -212,6 +226,33 @@
|
||||
<AndroidResource Include="Resources\values\colors.xml" />
|
||||
<AndroidResource Include="Resources\values\manifest.xml" />
|
||||
<AndroidResource Include="Resources\values-v30\manifest.xml" />
|
||||
<AndroidResource Include="Resources\drawable-v26\splash_screen_round.xml" />
|
||||
<AndroidResource Include="Resources\drawable\logo_rounded.xml" />
|
||||
<AndroidResource Include="Resources\drawable-night-v26\splash_screen_round.xml" />
|
||||
<AndroidResource Include="Resources\drawable\ic_notification.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\layout\validatable_input_dialog_layout.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\drawable\empty_uris_placeholder.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\drawable\empty_uris_placeholder_dark.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\drawable\empty_login_requests.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
<AndroidResource Include="Resources\drawable\empty_login_requests_dark.xml">
|
||||
<SubType></SubType>
|
||||
<Generator></Generator>
|
||||
</AndroidResource>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
|
||||
@@ -279,6 +320,8 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Resources\values-v30\" />
|
||||
<Folder Include="Resources\drawable-v26\" />
|
||||
<Folder Include="Resources\drawable-night-v26\" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
|
||||
</Project>
|
||||
10
src/Android/Autofill/AutofillConstants.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
public class AutofillConstants
|
||||
{
|
||||
public const string AutofillFramework = "autofillFramework";
|
||||
public const string AutofillFrameworkFillType = "autofillFrameworkFillType";
|
||||
public const string AutofillFrameworkUri = "autofillFrameworkUri";
|
||||
public const string AutofillFrameworkCipherId = "autofillFrameworkCipherId";
|
||||
}
|
||||
}
|
||||
42
src/Android/Autofill/AutofillExternalSelectionActivity.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Utilities;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Activity(
|
||||
NoHistory = true,
|
||||
LaunchMode = LaunchMode.SingleTop)]
|
||||
public class AutofillExternalSelectionActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||
{
|
||||
protected override void OnCreate(Bundle bundle)
|
||||
{
|
||||
Intent?.Validate();
|
||||
base.OnCreate(bundle);
|
||||
|
||||
var cipherId = Intent?.GetStringExtra(AutofillConstants.AutofillFrameworkCipherId);
|
||||
if (string.IsNullOrEmpty(cipherId))
|
||||
{
|
||||
SetResult(Result.Canceled);
|
||||
Finish();
|
||||
return;
|
||||
}
|
||||
|
||||
GetCipherAndPerformAutofillAsync(cipherId).FireAndForget();
|
||||
}
|
||||
|
||||
private async Task GetCipherAndPerformAutofillAsync(string cipherId)
|
||||
{
|
||||
var cipherService = ServiceContainer.Resolve<ICipherService>();
|
||||
var cipher = await cipherService.GetAsync(cipherId);
|
||||
var decCipher = await cipher.DecryptAsync();
|
||||
|
||||
var autofillHandler = ServiceContainer.Resolve<IAutofillHandler>();
|
||||
autofillHandler.Autofill(decCipher);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ using AndroidX.AutoFill.Inline;
|
||||
using AndroidX.AutoFill.Inline.V1;
|
||||
using Bit.Core.Abstractions;
|
||||
using SaveFlags = Android.Service.Autofill.SaveFlags;
|
||||
using Bit.Droid.Utilities;
|
||||
using Bit.Core.Services;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
@@ -53,6 +55,7 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
"alook.browser",
|
||||
"alook.browser.google",
|
||||
"app.vanadium.browser",
|
||||
"com.amazon.cloud9",
|
||||
"com.android.browser",
|
||||
"com.android.chrome",
|
||||
@@ -73,9 +76,11 @@ namespace Bit.Droid.Autofill
|
||||
"com.google.android.apps.chrome",
|
||||
"com.google.android.apps.chrome_dev",
|
||||
"com.google.android.captiveportallogin",
|
||||
"com.iode.firefox",
|
||||
"com.jamal2367.styx",
|
||||
"com.kiwibrowser.browser",
|
||||
"com.kiwibrowser.browser.dev",
|
||||
"com.lemurbrowser.exts",
|
||||
"com.microsoft.emmx",
|
||||
"com.microsoft.emmx.beta",
|
||||
"com.microsoft.emmx.canary",
|
||||
@@ -84,13 +89,16 @@ namespace Bit.Droid.Autofill
|
||||
"com.mmbox.xbrowser",
|
||||
"com.mycompany.app.soulbrowser",
|
||||
"com.naver.whale",
|
||||
"com.neeva.app",
|
||||
"com.opera.browser",
|
||||
"com.opera.browser.beta",
|
||||
"com.opera.gx",
|
||||
"com.opera.mini.native",
|
||||
"com.opera.mini.native.beta",
|
||||
"com.opera.touch",
|
||||
"com.qflair.browserq",
|
||||
"com.qwant.liberty",
|
||||
"com.rainsee.create",
|
||||
"com.sec.android.app.sbrowser",
|
||||
"com.sec.android.app.sbrowser.beta",
|
||||
"com.stoutner.privacybrowser.free",
|
||||
@@ -99,6 +107,9 @@ namespace Bit.Droid.Autofill
|
||||
"com.vivaldi.browser.snapshot",
|
||||
"com.vivaldi.browser.sopranos",
|
||||
"com.yandex.browser",
|
||||
"com.yjllq.internet",
|
||||
"com.yjllq.kito",
|
||||
"com.yujian.ResideMenuDemo",
|
||||
"com.z28j.feel",
|
||||
"idm.internet.download.manager",
|
||||
"idm.internet.download.manager.adm.lite",
|
||||
@@ -106,6 +117,7 @@ namespace Bit.Droid.Autofill
|
||||
"io.github.forkmaintainers.iceraven",
|
||||
"mark.via",
|
||||
"mark.via.gp",
|
||||
"net.dezor.browser",
|
||||
"net.slions.fulguris.full.download",
|
||||
"net.slions.fulguris.full.download.debug",
|
||||
"net.slions.fulguris.full.playstore",
|
||||
@@ -116,6 +128,7 @@ namespace Bit.Droid.Autofill
|
||||
"org.bromite.chromium",
|
||||
"org.chromium.chrome",
|
||||
"org.codeaurora.swe.browser",
|
||||
"org.cromite.cromite",
|
||||
"org.gnu.icecat",
|
||||
"org.mozilla.fenix",
|
||||
"org.mozilla.fenix.nightly",
|
||||
@@ -141,8 +154,9 @@ namespace Bit.Droid.Autofill
|
||||
"androidapp://com.oneplus.applocker",
|
||||
};
|
||||
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
|
||||
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService, IUserVerificationService userVerificationService)
|
||||
{
|
||||
var userHasMasterPassword = await userVerificationService.HasMasterPasswordAsync();
|
||||
if (parser.FieldCollection.FillableForLogin)
|
||||
{
|
||||
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(parser.Uri);
|
||||
@@ -150,14 +164,14 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
var allCiphers = ciphers.Item1.ToList();
|
||||
allCiphers.AddRange(ciphers.Item2.ToList());
|
||||
var nonPromptCiphers = allCiphers.Where(cipher => cipher.Reprompt == CipherRepromptType.None);
|
||||
var nonPromptCiphers = allCiphers.Where(cipher => !userHasMasterPassword || cipher.Reprompt == CipherRepromptType.None);
|
||||
return nonPromptCiphers.Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
}
|
||||
else if (parser.FieldCollection.FillableForCard)
|
||||
{
|
||||
var ciphers = await cipherService.GetAllDecryptedAsync();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card && c.Reprompt == CipherRepromptType.None).Select(c => new FilledItem(c)).ToList();
|
||||
return ciphers.Where(c => c.Type == CipherType.Card && (!userHasMasterPassword || c.Reprompt == CipherRepromptType.None)).Select(c => new FilledItem(c)).ToList();
|
||||
}
|
||||
return new List<FilledItem>();
|
||||
}
|
||||
@@ -204,7 +218,7 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
}
|
||||
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
|
||||
inlinePresentationSpec);
|
||||
true, inlinePresentationSpec);
|
||||
if (dataset != null)
|
||||
{
|
||||
responseBuilder.AddDataset(dataset);
|
||||
@@ -218,7 +232,7 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
|
||||
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
|
||||
InlinePresentationSpec inlinePresentationSpec = null)
|
||||
bool includeAuthIntent, InlinePresentationSpec inlinePresentationSpec = null)
|
||||
{
|
||||
var overlayPresentation = BuildOverlayPresentation(
|
||||
filledItem.Name,
|
||||
@@ -239,6 +253,15 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
datasetBuilder.SetInlinePresentation(inlinePresentation);
|
||||
}
|
||||
if (includeAuthIntent)
|
||||
{
|
||||
var intent = new Intent(context, typeof(AutofillExternalSelectionActivity));
|
||||
intent.PutExtra(AutofillConstants.AutofillFramework, true);
|
||||
intent.PutExtra(AutofillConstants.AutofillFrameworkCipherId, filledItem.Id);
|
||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
|
||||
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
|
||||
}
|
||||
if (filledItem.ApplyToFields(fields, datasetBuilder))
|
||||
{
|
||||
return datasetBuilder.Build();
|
||||
@@ -250,26 +273,26 @@ namespace Bit.Droid.Autofill
|
||||
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
|
||||
{
|
||||
var intent = new Intent(context, typeof(MainActivity));
|
||||
intent.PutExtra("autofillFramework", true);
|
||||
intent.PutExtra(AutofillConstants.AutofillFramework, true);
|
||||
if (fields.FillableForLogin)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
|
||||
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Login);
|
||||
}
|
||||
else if (fields.FillableForCard)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
|
||||
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Card);
|
||||
}
|
||||
else if (fields.FillableForIdentity)
|
||||
{
|
||||
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
|
||||
intent.PutExtra(AutofillConstants.AutofillFrameworkFillType, (int)CipherType.Identity);
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
intent.PutExtra("autofillFrameworkUri", uri);
|
||||
intent.PutExtra(AutofillConstants.AutofillFrameworkUri, uri);
|
||||
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
|
||||
PendingIntentFlags.CancelCurrent);
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.CancelCurrent, true));
|
||||
|
||||
var overlayPresentation = BuildOverlayPresentation(
|
||||
AppResources.AutofillWithBitwarden,
|
||||
@@ -322,7 +345,7 @@ namespace Bit.Droid.Autofill
|
||||
// InlinePresentation requires nonNull pending intent (even though we only utilize one for the
|
||||
// "my vault" presentation) so we're including an empty one here
|
||||
pendingIntent = PendingIntent.GetService(context, 0, new Intent(),
|
||||
PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent);
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.OneShot | PendingIntentFlags.UpdateCurrent, true));
|
||||
}
|
||||
var slice = CreateInlinePresentationSlice(
|
||||
inlinePresentationSpec,
|
||||
|
||||
@@ -11,11 +11,12 @@ using Android.Widget;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
namespace Bit.Droid.Autofill
|
||||
{
|
||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden")]
|
||||
[Service(Permission = Manifest.Permission.BindAutofillService, Label = "Bitwarden", Exported = true)]
|
||||
[IntentFilter(new string[] { "android.service.autofill.AutofillService" })]
|
||||
[MetaData("android.autofill", Resource = "@xml/autofillservice")]
|
||||
[Register("com.x8bit.bitwarden.Autofill.AutofillService")]
|
||||
@@ -26,6 +27,7 @@ namespace Bit.Droid.Autofill
|
||||
private IPolicyService _policyService;
|
||||
private IStateService _stateService;
|
||||
private LazyResolve<ILogger> _logger = new LazyResolve<ILogger>("logger");
|
||||
private IUserVerificationService _userVerificationService;
|
||||
|
||||
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
|
||||
FillCallback callback)
|
||||
@@ -64,11 +66,9 @@ namespace Bit.Droid.Autofill
|
||||
var locked = await _vaultTimeoutService.IsLockedAsync();
|
||||
if (!locked)
|
||||
{
|
||||
if (_cipherService == null)
|
||||
{
|
||||
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
|
||||
}
|
||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService);
|
||||
_cipherService ??= ServiceContainer.Resolve<ICipherService>();
|
||||
_userVerificationService ??= ServiceContainer.Resolve<IUserVerificationService>();
|
||||
items = await AutofillHelpers.GetFillItemsAsync(parser, _cipherService, _userVerificationService);
|
||||
}
|
||||
|
||||
// build response
|
||||
@@ -134,7 +134,7 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
case CipherType.Login:
|
||||
intent.PutExtra("autofillFrameworkName", parser.Uri
|
||||
.Replace(Constants.AndroidAppProtocol, string.Empty)
|
||||
.Replace(Core.Constants.AndroidAppProtocol, string.Empty)
|
||||
.Replace("https://", string.Empty)
|
||||
.Replace("http://", string.Empty));
|
||||
intent.PutExtra("autofillFrameworkUri", parser.Uri);
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Bit.Droid.Autofill
|
||||
private List<Field> _passwordFields = null;
|
||||
private List<Field> _usernameFields = null;
|
||||
private HashSet<string> _ignoreSearchTerms = new HashSet<string> { "search", "find", "recipient", "edit" };
|
||||
private HashSet<string> _usernameTerms = new HashSet<string> { "email", "phone", "username" };
|
||||
private HashSet<string> _passwordTerms = new HashSet<string> { "password", "pswd" };
|
||||
|
||||
public List<AutofillId> AutofillIds { get; private set; } = new List<AutofillId>();
|
||||
@@ -53,15 +54,14 @@ namespace Bit.Droid.Autofill
|
||||
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
|
||||
{
|
||||
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
|
||||
return _passwordFields;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||
if (!_passwordFields.Any())
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
|
||||
if (!_passwordFields.Any())
|
||||
{
|
||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||
}
|
||||
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
|
||||
}
|
||||
return _passwordFields;
|
||||
}
|
||||
@@ -86,19 +86,26 @@ namespace Bit.Droid.Autofill
|
||||
{
|
||||
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var passwordField in PasswordFields)
|
||||
if (_usernameFields.Any())
|
||||
{
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||
.LastOrDefault();
|
||||
if (usernameField != null)
|
||||
{
|
||||
_usernameFields.Add(usernameField);
|
||||
}
|
||||
return _usernameFields;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var passwordField in PasswordFields)
|
||||
{
|
||||
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
|
||||
.LastOrDefault();
|
||||
if (usernameField != null)
|
||||
{
|
||||
_usernameFields.Add(usernameField);
|
||||
}
|
||||
}
|
||||
|
||||
if (!_usernameFields.Any())
|
||||
{
|
||||
_usernameFields = Fields.Where(f => FieldIsUsername(f)).ToList();
|
||||
}
|
||||
return _usernameFields;
|
||||
}
|
||||
}
|
||||
@@ -321,13 +328,23 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
|
||||
return inputTypePassword && !ValueContainsAnyTerms(f.IdEntry, _ignoreSearchTerms) &&
|
||||
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms);
|
||||
!ValueContainsAnyTerms(f.Hint, _ignoreSearchTerms) && !FieldIsUsername(f);
|
||||
}
|
||||
|
||||
private bool FieldHasPasswordTerms(Field f)
|
||||
{
|
||||
return ValueContainsAnyTerms(f.IdEntry, _passwordTerms) || ValueContainsAnyTerms(f.Hint, _passwordTerms);
|
||||
}
|
||||
|
||||
private bool FieldIsUsername(Field f)
|
||||
{
|
||||
return f.InputType.HasFlag(InputTypes.TextVariationWebEmailAddress) || FieldHasUsernameTerms(f);
|
||||
}
|
||||
|
||||
private bool FieldHasUsernameTerms(Field f)
|
||||
{
|
||||
return ValueContainsAnyTerms(f.IdEntry, _usernameTerms) || ValueContainsAnyTerms(f.Hint, _usernameTerms);
|
||||
}
|
||||
|
||||
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
|
||||
{
|
||||
@@ -339,4 +356,4 @@ namespace Bit.Droid.Autofill
|
||||
return terms.Any(t => lowerValue.Contains(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace Bit.Droid.Autofill
|
||||
|
||||
public FilledItem(CipherView cipher)
|
||||
{
|
||||
Id = cipher.Id;
|
||||
Name = cipher.Name;
|
||||
Type = cipher.Type;
|
||||
Subtitle = cipher.SubTitle;
|
||||
@@ -55,6 +56,7 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
}
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Subtitle { get; set; } = string.Empty;
|
||||
public int Icon { get; set; } = Resource.Drawable.login;
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace Bit.Droid.Autofill
|
||||
}
|
||||
else
|
||||
{
|
||||
_uri = string.Concat(Constants.AndroidAppProtocol, PackageName);
|
||||
_uri = string.Concat(Core.Constants.AndroidAppProtocol, PackageName);
|
||||
}
|
||||
return _uri;
|
||||
}
|
||||
|
||||
13
src/Android/Constants.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Bit.Droid
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public const string PACKAGE_NAME = "com.x8bit.bitwarden";
|
||||
public const string TEMP_CAMERA_IMAGE_NAME = "temp_camera_image.jpg";
|
||||
|
||||
/// <summary>
|
||||
/// This directory must also be declared in filepaths.xml
|
||||
/// </summary>
|
||||
public const string TEMP_CAMERA_IMAGE_DIR = "camera_temp";
|
||||
}
|
||||
}
|
||||
24
src/Android/Effects/NoEmojiKeyboardEffect.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportEffect(typeof(NoEmojiKeyboardEffect), nameof(NoEmojiKeyboardEffect))]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class NoEmojiKeyboardEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if (Control is EditText editText)
|
||||
{
|
||||
editText.InputType = Android.Text.InputTypes.ClassText | Android.Text.InputTypes.TextVariationVisiblePassword | Android.Text.InputTypes.TextFlagMultiLine;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetached()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
src/Android/Effects/RemoveFontPaddingEffect.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Android.Widget;
|
||||
using Bit.Droid.Effects;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportEffect(typeof(RemoveFontPaddingEffect), nameof(RemoveFontPaddingEffect))]
|
||||
namespace Bit.Droid.Effects
|
||||
{
|
||||
public class RemoveFontPaddingEffect : PlatformEffect
|
||||
{
|
||||
protected override void OnAttached()
|
||||
{
|
||||
if (Control is TextView textView)
|
||||
{
|
||||
textView.SetIncludeFontPadding(false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDetached()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,20 +5,27 @@ using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Content.Res;
|
||||
using Android.Nfc;
|
||||
using Android.OS;
|
||||
using Android.Runtime;
|
||||
using AndroidX.Core.Content;
|
||||
using Android.Views;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Resources;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Autofill;
|
||||
using Bit.Droid.Receivers;
|
||||
using Bit.Droid.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Xamarin.Essentials;
|
||||
using ZXing.Net.Mobile.Android;
|
||||
using FileProvider = AndroidX.Core.Content.FileProvider;
|
||||
|
||||
namespace Bit.Droid
|
||||
{
|
||||
@@ -30,11 +37,14 @@ namespace Bit.Droid
|
||||
public class MainActivity : Xamarin.Forms.Platform.Android.FormsAppCompatActivity
|
||||
{
|
||||
private IDeviceActionService _deviceActionService;
|
||||
private IFileService _fileService;
|
||||
private IMessagingService _messagingService;
|
||||
private IBroadcasterService _broadcasterService;
|
||||
private IStateService _stateService;
|
||||
private IAppIdService _appIdService;
|
||||
private IEventService _eventService;
|
||||
private IPushNotificationListenerService _pushNotificationListenerService;
|
||||
private ILogger _logger;
|
||||
private PendingIntent _eventUploadPendingIntent;
|
||||
private AppOptions _appOptions;
|
||||
private string _activityKey = $"{nameof(MainActivity)}_{Java.Lang.JavaSystem.CurrentTimeMillis().ToString()}";
|
||||
@@ -45,17 +55,20 @@ namespace Bit.Droid
|
||||
{
|
||||
var eventUploadIntent = new Intent(this, typeof(EventUploadReceiver));
|
||||
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
|
||||
PendingIntentFlags.UpdateCurrent);
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false));
|
||||
|
||||
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
|
||||
StrictMode.SetThreadPolicy(policy);
|
||||
|
||||
_deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
_fileService = ServiceContainer.Resolve<IFileService>();
|
||||
_messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
|
||||
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
|
||||
_stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
_appIdService = ServiceContainer.Resolve<IAppIdService>("appIdService");
|
||||
_eventService = ServiceContainer.Resolve<IEventService>("eventService");
|
||||
_pushNotificationListenerService = ServiceContainer.Resolve<IPushNotificationListenerService>();
|
||||
_logger = ServiceContainer.Resolve<ILogger>("logger");
|
||||
|
||||
TabLayoutResource = Resource.Layout.Tabbar;
|
||||
ToolbarResource = Resource.Layout.Toolbar;
|
||||
@@ -64,15 +77,13 @@ namespace Bit.Droid
|
||||
Intent?.Validate();
|
||||
|
||||
base.OnCreate(savedInstanceState);
|
||||
if (!CoreHelpers.InDebugMode())
|
||||
|
||||
_deviceActionService.SetScreenCaptureAllowedAsync().FireAndForget(_ =>
|
||||
{
|
||||
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
|
||||
}
|
||||
});
|
||||
|
||||
#if !DEBUG && !FDROID
|
||||
var appCenterHelper = new AppCenterHelper(_appIdService, _stateService);
|
||||
var appCenterTask = appCenterHelper.InitAsync();
|
||||
#endif
|
||||
_logger.InitAsync();
|
||||
|
||||
var toplayout = Window?.DecorView?.RootView;
|
||||
if (toplayout != null)
|
||||
@@ -83,7 +94,9 @@ namespace Bit.Droid
|
||||
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
|
||||
Xamarin.Forms.Forms.Init(this, savedInstanceState);
|
||||
_appOptions = GetOptions();
|
||||
CreateNotificationChannel();
|
||||
LoadApplication(new App.App(_appOptions));
|
||||
DisableAndroidFontScale();
|
||||
|
||||
_broadcasterService.Subscribe(_activityKey, (message) =>
|
||||
{
|
||||
@@ -103,7 +116,7 @@ namespace Bit.Droid
|
||||
{
|
||||
ListenYubiKey((bool)message.Data);
|
||||
}
|
||||
else if (message.Command == "updatedTheme")
|
||||
else if (message.Command is ThemeManager.UPDATED_THEME_MESSAGE_KEY)
|
||||
{
|
||||
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => AppearanceAdjustments());
|
||||
}
|
||||
@@ -139,6 +152,15 @@ namespace Bit.Droid
|
||||
AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(this)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
if (Intent?.GetStringExtra(Core.Constants.NotificationData) is string notificationDataJson)
|
||||
{
|
||||
var notificationType = JToken.Parse(notificationDataJson).SelectToken(Core.Constants.NotificationDataType);
|
||||
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
|
||||
{
|
||||
_pushNotificationListenerService.OnNotificationTapped(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnNewIntent(Intent intent)
|
||||
@@ -148,7 +170,7 @@ namespace Bit.Droid
|
||||
{
|
||||
if (intent?.GetStringExtra("uri") is string uri)
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToAutofillCiphers");
|
||||
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_AUTOFILL_CIPHERS_MESSAGE);
|
||||
if (_appOptions != null)
|
||||
{
|
||||
_appOptions.Uri = uri;
|
||||
@@ -156,7 +178,7 @@ namespace Bit.Droid
|
||||
}
|
||||
else if (intent.GetBooleanExtra("generatorTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabGenerator");
|
||||
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_GENERATOR_MESSAGE);
|
||||
if (_appOptions != null)
|
||||
{
|
||||
_appOptions.GeneratorTile = true;
|
||||
@@ -164,7 +186,7 @@ namespace Bit.Droid
|
||||
}
|
||||
else if (intent.GetBooleanExtra("myVaultTile", false))
|
||||
{
|
||||
_messagingService.Send("popAllAndGoToTabMyVault");
|
||||
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_MYVAULT_MESSAGE);
|
||||
if (_appOptions != null)
|
||||
{
|
||||
_appOptions.MyVaultTile = true;
|
||||
@@ -176,7 +198,7 @@ namespace Bit.Droid
|
||||
{
|
||||
_appOptions.CreateSend = GetCreateSendRequest(intent);
|
||||
}
|
||||
_messagingService.Send("popAllAndGoToTabSend");
|
||||
_messagingService.Send(App.App.POP_ALL_AND_GO_TO_TAB_SEND_MESSAGE);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -192,13 +214,13 @@ namespace Bit.Droid
|
||||
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
|
||||
[GeneratedEnum] Permission[] grantResults)
|
||||
{
|
||||
if (requestCode == Constants.SelectFilePermissionRequestCode)
|
||||
if (requestCode == Core.Constants.SelectFilePermissionRequestCode)
|
||||
{
|
||||
if (grantResults.Any(r => r != Permission.Granted))
|
||||
{
|
||||
_messagingService.Send("selectFileCameraPermissionDenied");
|
||||
}
|
||||
await _deviceActionService.SelectFileAsync();
|
||||
await _fileService.SelectFileAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -211,29 +233,33 @@ namespace Bit.Droid
|
||||
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
|
||||
{
|
||||
if (resultCode == Result.Ok &&
|
||||
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
|
||||
(requestCode == Core.Constants.SelectFileRequestCode || requestCode == Core.Constants.SaveFileRequestCode))
|
||||
{
|
||||
Android.Net.Uri uri = null;
|
||||
string fileName = null;
|
||||
if (data != null && data.Data != null)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||
if (data.Data.ToString()?.Contains(Constants.PACKAGE_NAME) != true)
|
||||
{
|
||||
uri = data.Data;
|
||||
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// camera
|
||||
var file = new Java.IO.File(FilesDir, "temp_camera_photo.jpg");
|
||||
var tmpDir = new Java.IO.File(FilesDir, Constants.TEMP_CAMERA_IMAGE_DIR);
|
||||
var file = new Java.IO.File(tmpDir, Constants.TEMP_CAMERA_IMAGE_NAME);
|
||||
uri = FileProvider.GetUriForFile(this, "com.x8bit.bitwarden.fileprovider", file);
|
||||
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
|
||||
}
|
||||
|
||||
if (uri == null)
|
||||
if (uri == null || fileName == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestCode == Constants.SaveFileRequestCode)
|
||||
if (requestCode == Core.Constants.SaveFileRequestCode)
|
||||
{
|
||||
_messagingService.Send("selectSaveFileResult",
|
||||
new Tuple<string, string>(uri.ToString(), fileName));
|
||||
@@ -274,7 +300,7 @@ namespace Bit.Droid
|
||||
{
|
||||
var intent = new Intent(this, Class);
|
||||
intent.AddFlags(ActivityFlags.SingleTop);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, 0);
|
||||
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, AndroidHelpers.AddPendingIntentMutabilityFlag(0, true));
|
||||
// register for all NDEF tags starting with http och https
|
||||
var ndef = new IntentFilter(NfcAdapter.ActionNdefDiscovered);
|
||||
ndef.AddDataScheme("http");
|
||||
@@ -301,13 +327,13 @@ namespace Bit.Droid
|
||||
{
|
||||
var options = new AppOptions
|
||||
{
|
||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra("autofillFrameworkUri"),
|
||||
Uri = Intent.GetStringExtra("uri") ?? Intent.GetStringExtra(AutofillConstants.AutofillFrameworkUri),
|
||||
MyVaultTile = Intent.GetBooleanExtra("myVaultTile", false),
|
||||
GeneratorTile = Intent.GetBooleanExtra("generatorTile", false),
|
||||
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false),
|
||||
FromAutofillFramework = Intent.GetBooleanExtra(AutofillConstants.AutofillFramework, false),
|
||||
CreateSend = GetCreateSendRequest(Intent)
|
||||
};
|
||||
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
|
||||
var fillType = Intent.GetIntExtra(AutofillConstants.AutofillFrameworkFillType, 0);
|
||||
if (fillType > 0)
|
||||
{
|
||||
options.FillType = (CipherType)fillType;
|
||||
@@ -402,5 +428,38 @@ namespace Bit.Droid
|
||||
alarmManager.Cancel(_eventUploadPendingIntent);
|
||||
await _eventService.UploadEventsAsync();
|
||||
}
|
||||
|
||||
private void CreateNotificationChannel()
|
||||
{
|
||||
#if !FDROID
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
// Notification channels are new in API 26 (and not a part of the
|
||||
// support library). There is no need to create a notification
|
||||
// channel on older versions of Android.
|
||||
return;
|
||||
}
|
||||
|
||||
var channel = new NotificationChannel(Core.Constants.AndroidNotificationChannelId, AppResources.AllNotifications, NotificationImportance.Default);
|
||||
if(GetSystemService(NotificationService) is NotificationManager notificationManager)
|
||||
{
|
||||
notificationManager.CreateNotificationChannel(channel);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private void DisableAndroidFontScale()
|
||||
{
|
||||
try
|
||||
{
|
||||
//As we are using NamedSizes the xamarin will change the font size. So we are disabling the Android scaling.
|
||||
Resources.Configuration.FontScale = 1f;
|
||||
BaseContext.Resources.DisplayMetrics.ScaledDensity = Resources.Configuration.FontScale * (float)DeviceDisplay.MainDisplayInfo.Density;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Exception(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ 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 Xamarin.Android.Net;
|
||||
@@ -20,6 +19,9 @@ using System.Net.Http;
|
||||
using System.Net;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Pages;
|
||||
using Bit.App.Utilities.AccountManagement;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Core.Enums;
|
||||
#if !FDROID
|
||||
using Android.Gms.Security;
|
||||
#endif
|
||||
@@ -44,9 +46,17 @@ namespace Bit.Droid
|
||||
if (ServiceContainer.RegisteredServices.Count == 0)
|
||||
{
|
||||
RegisterLocalServices();
|
||||
|
||||
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
|
||||
Constants.AndroidAllClearCipherCacheKeys);
|
||||
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Core.Constants.ClearCiphersCacheKey,
|
||||
Core.Constants.AndroidAllClearCipherCacheKeys);
|
||||
|
||||
ServiceContainer.Register<IWatchDeviceService>(new WatchDeviceService(ServiceContainer.Resolve<ICipherService>(),
|
||||
ServiceContainer.Resolve<IEnvironmentService>(),
|
||||
ServiceContainer.Resolve<IStateService>(),
|
||||
ServiceContainer.Resolve<IVaultTimeoutService>()));
|
||||
|
||||
InitializeAppSetup();
|
||||
|
||||
// TODO: Update when https://github.com/bitwarden/mobile/pull/1662 gets merged
|
||||
var deleteAccountActionFlowExecutioner = new DeleteAccountActionFlowExecutioner(
|
||||
@@ -58,10 +68,23 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<IDeleteAccountActionFlowExecutioner>("deleteAccountActionFlowExecutioner", deleteAccountActionFlowExecutioner);
|
||||
|
||||
var verificationActionsFlowHelper = new VerificationActionsFlowHelper(
|
||||
ServiceContainer.Resolve<IKeyConnectorService>("keyConnectorService"),
|
||||
ServiceContainer.Resolve<IPasswordRepromptService>("passwordRepromptService"),
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"));
|
||||
ServiceContainer.Resolve<ICryptoService>("cryptoService"),
|
||||
ServiceContainer.Resolve<IUserVerificationService>());
|
||||
ServiceContainer.Register<IVerificationActionsFlowHelper>("verificationActionsFlowHelper", verificationActionsFlowHelper);
|
||||
|
||||
var accountsManager = new AccountsManager(
|
||||
ServiceContainer.Resolve<IBroadcasterService>("broadcasterService"),
|
||||
ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService"),
|
||||
ServiceContainer.Resolve<IStorageService>("secureStorageService"),
|
||||
ServiceContainer.Resolve<IStateService>("stateService"),
|
||||
ServiceContainer.Resolve<IPlatformUtilsService>("platformUtilsService"),
|
||||
ServiceContainer.Resolve<IAuthService>("authService"),
|
||||
ServiceContainer.Resolve<ILogger>("logger"),
|
||||
ServiceContainer.Resolve<IMessagingService>("messagingService"),
|
||||
ServiceContainer.Resolve<IWatchDeviceService>(),
|
||||
ServiceContainer.Resolve<IConditionedAwaiterManager>());
|
||||
ServiceContainer.Register<IAccountsManager>("accountsManager", accountsManager);
|
||||
}
|
||||
#if !FDROID
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
|
||||
@@ -90,12 +113,13 @@ namespace Bit.Droid
|
||||
{
|
||||
ServiceContainer.Register<INativeLogService>("nativeLogService", new AndroidLogService());
|
||||
#if FDROID
|
||||
ServiceContainer.Register<ILogger>("logger", new StubLogger());
|
||||
var logger = new StubLogger();
|
||||
#elif DEBUG
|
||||
ServiceContainer.Register<ILogger>("logger", DebugLogger.Instance);
|
||||
var logger = DebugLogger.Instance;
|
||||
#else
|
||||
ServiceContainer.Register<ILogger>("logger", Logger.Instance);
|
||||
var logger = Logger.Instance;
|
||||
#endif
|
||||
ServiceContainer.Register("logger", logger);
|
||||
|
||||
// Note: This might cause a race condition. Investigate more.
|
||||
Task.Run(() =>
|
||||
@@ -115,24 +139,30 @@ namespace Bit.Droid
|
||||
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
|
||||
var liteDbStorage = new LiteDbStorageService(Path.Combine(documentsPath, "bitwarden.db"));
|
||||
var localizeService = new LocalizeService();
|
||||
var broadcasterService = new BroadcasterService();
|
||||
var broadcasterService = new BroadcasterService(logger);
|
||||
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 stateService = new StateService(mobileStorageService, secureStorageService);
|
||||
var storageMediatorService = new StorageMediatorService(mobileStorageService, secureStorageService, preferencesStorage);
|
||||
var stateService = new StateService(mobileStorageService, secureStorageService, storageMediatorService, messagingService);
|
||||
var stateMigrationService =
|
||||
new StateMigrationService(liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var deviceActionService = new DeviceActionService(stateService, messagingService,
|
||||
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
|
||||
broadcasterService);
|
||||
var biometricService = new BiometricService();
|
||||
new StateMigrationService(DeviceType.Android, liteDbStorage, preferencesStorage, secureStorageService);
|
||||
var clipboardService = new ClipboardService(stateService);
|
||||
var deviceActionService = new DeviceActionService(stateService, messagingService);
|
||||
var fileService = new FileService(stateService, broadcasterService);
|
||||
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, clipboardService,
|
||||
messagingService, broadcasterService);
|
||||
var autofillHandler = new AutofillHandler(stateService, messagingService, clipboardService,
|
||||
platformUtilsService, new LazyResolve<IEventService>());
|
||||
var cryptoFunctionService = new PclCryptoFunctionService(cryptoPrimitiveService);
|
||||
var cryptoService = new CryptoService(stateService, cryptoFunctionService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService);
|
||||
var biometricService = new BiometricService(stateService, cryptoService);
|
||||
var userPinService = new UserPinService(stateService, cryptoService);
|
||||
var passwordRepromptService = new MobilePasswordRepromptService(platformUtilsService, cryptoService, stateService);
|
||||
|
||||
ServiceContainer.Register<ISynchronousStorageService>(preferencesStorage);
|
||||
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
|
||||
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
|
||||
ServiceContainer.Register<ILocalizeService>("localizeService", localizeService);
|
||||
@@ -140,15 +170,20 @@ namespace Bit.Droid
|
||||
ServiceContainer.Register<ICryptoPrimitiveService>("cryptoPrimitiveService", cryptoPrimitiveService);
|
||||
ServiceContainer.Register<IStorageService>("storageService", mobileStorageService);
|
||||
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
|
||||
ServiceContainer.Register<IStorageMediatorService>(storageMediatorService);
|
||||
ServiceContainer.Register<IStateService>("stateService", stateService);
|
||||
ServiceContainer.Register<IStateMigrationService>("stateMigrationService", stateMigrationService);
|
||||
ServiceContainer.Register<IClipboardService>("clipboardService", new ClipboardService(stateService));
|
||||
ServiceContainer.Register<IClipboardService>("clipboardService", clipboardService);
|
||||
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
|
||||
ServiceContainer.Register<IFileService>(fileService);
|
||||
ServiceContainer.Register<IAutofillHandler>(autofillHandler);
|
||||
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
|
||||
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
|
||||
ServiceContainer.Register<ICryptoFunctionService>("cryptoFunctionService", cryptoFunctionService);
|
||||
ServiceContainer.Register<ICryptoService>("cryptoService", cryptoService);
|
||||
ServiceContainer.Register<IPasswordRepromptService>("passwordRepromptService", passwordRepromptService);
|
||||
ServiceContainer.Register<IAvatarImageSourcePool>("avatarImageSourcePool", new AvatarImageSourcePool());
|
||||
ServiceContainer.Register<IUserPinService>(userPinService);
|
||||
|
||||
// Push
|
||||
#if FDROID
|
||||
@@ -169,7 +204,9 @@ namespace Bit.Droid
|
||||
|
||||
private void Bootstrap()
|
||||
{
|
||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService).Init();
|
||||
var locale = ServiceContainer.Resolve<IStateService>().GetLocale();
|
||||
(ServiceContainer.Resolve<II18nService>("i18nService") as MobileI18nService)
|
||||
.Init(locale != null ? new System.Globalization.CultureInfo(locale) : null);
|
||||
ServiceContainer.Resolve<IAuthService>("authService").Init();
|
||||
// Note: This is not awaited
|
||||
var bootstrapTask = BootstrapAsync();
|
||||
@@ -179,5 +216,12 @@ namespace Bit.Droid
|
||||
{
|
||||
await ServiceContainer.Resolve<IEnvironmentService>("environmentService").SetUrlsFromStorageAsync();
|
||||
}
|
||||
|
||||
private void InitializeAppSetup()
|
||||
{
|
||||
var appSetup = new AppSetup();
|
||||
appSetup.InitializeServicesLastChance();
|
||||
ServiceContainer.Register<IAppSetup>("appSetup", appSetup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,57 +1,55 @@
|
||||
<?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.18.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30"/>
|
||||
|
||||
<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.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<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/LaunchTheme" 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="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/>
|
||||
</provider>
|
||||
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1"/>
|
||||
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions"/>
|
||||
|
||||
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true"/>
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true"/>
|
||||
|
||||
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true"/>
|
||||
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
|
||||
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<data android:mimeType="application/*"/>
|
||||
<data android:mimeType="image/*"/>
|
||||
<data android:mimeType="video/*"/>
|
||||
<data android:mimeType="text/*"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
<!-- Package visibility (for Android 11+) -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="*"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
<?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="2023.12.1" android:installLocation="internalOnly" package="com.x8bit.bitwarden">
|
||||
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
|
||||
<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.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<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/LaunchTheme" 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="androidx.core.content.FileProvider" android:authorities="com.x8bit.bitwarden.fileprovider" android:exported="false" android:grantUriPermissions="true">
|
||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths" />
|
||||
</provider>
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1" />
|
||||
<meta-data android:name="android.content.APP_RESTRICTIONS" android:resource="@xml/app_restrictions" />
|
||||
<!-- Support for Samsung "Multi Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.enable" android:value="true" />
|
||||
<meta-data android:name="com.samsung.android.sdk.multiwindow.penwindow.enable" android:value="true" />
|
||||
<!-- Support for LG "Dual Window" mode (for Android < 7.0 users) -->
|
||||
<meta-data android:name="com.lge.support.SPLIT_WINDOW" android:value="true" />
|
||||
<!-- Declare MainActivity manually so we can set LaunchMode using API dependant resource -->
|
||||
<activity android:name="com.x8bit.bitwarden.MainActivity" android:configChanges="keyboard|keyboardHidden|navigation|orientation|screenSize|uiMode" android:exported="true" android:icon="@mipmap/ic_launcher" android:label="Bitwarden" android:launchMode="@integer/launchModeAPIlevel" android:theme="@style/LaunchTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:mimeType="application/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
<data android:mimeType="text/*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
<!-- Support for Xamarin.Essentials.Browser.OpenAsync (for Android > 11) -->
|
||||
<!-- Related docs: https://learn.microsoft.com/en-us/xamarin/essentials/open-browser?tabs=android -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="http" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -1,7 +1,9 @@
|
||||
#if !FDROID
|
||||
using System;
|
||||
using Android.App;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Firebase.Messaging;
|
||||
using Newtonsoft.Json;
|
||||
@@ -16,34 +18,41 @@ namespace Bit.Droid.Push
|
||||
{
|
||||
public async override void OnNewToken(string token)
|
||||
{
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
try {
|
||||
var stateService = ServiceContainer.Resolve<IStateService>("stateService");
|
||||
var pushNotificationService = ServiceContainer.Resolve<IPushNotificationService>("pushNotificationService");
|
||||
|
||||
await stateService.SetPushRegisteredTokenAsync(token);
|
||||
await pushNotificationService.RegisterAsync();
|
||||
await stateService.SetPushRegisteredTokenAsync(token);
|
||||
await pushNotificationService.RegisterAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Instance.Exception(ex);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (message?.Data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
|
||||
if (data == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var obj = JObject.Parse(data);
|
||||
var listener = ServiceContainer.Resolve<IPushNotificationListenerService>(
|
||||
"pushNotificationListenerService");
|
||||
await listener.OnMessageAsync(obj, Device.Android);
|
||||
}
|
||||
catch (JsonReaderException ex)
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex.ToString());
|
||||
Logger.Instance.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
@@ -8,7 +9,17 @@ namespace Bit.Droid.Receivers
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
|
||||
if (clipboardManager == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ClearPrimaryClip is supported down to API 28 with mixed results, so we're requiring 33+ instead
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
{
|
||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", " ");
|
||||
return;
|
||||
}
|
||||
clipboardManager.ClearPrimaryClip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
41
src/Android/Receivers/NotificationDismissReceiver.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using Android.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.App.Services;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using CoreConstants = Bit.Core.Constants;
|
||||
|
||||
namespace Bit.Droid.Receivers
|
||||
{
|
||||
[BroadcastReceiver(Name = Constants.PACKAGE_NAME + "." + nameof(NotificationDismissReceiver), Exported = false)]
|
||||
public class NotificationDismissReceiver : BroadcastReceiver
|
||||
{
|
||||
private readonly LazyResolve<IPushNotificationListenerService> _pushNotificationListenerService = new LazyResolve<IPushNotificationListenerService>();
|
||||
private readonly LazyResolve<ILogger> _logger = new LazyResolve<ILogger>();
|
||||
|
||||
public override void OnReceive(Context context, Intent intent)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (intent?.GetStringExtra(CoreConstants.NotificationData) is string notificationDataJson)
|
||||
{
|
||||
var notificationType = JToken.Parse(notificationDataJson).SelectToken(CoreConstants.NotificationDataType);
|
||||
if (notificationType.ToString() == PasswordlessNotificationData.TYPE)
|
||||
{
|
||||
_pushNotificationListenerService.Value.OnNotificationDismissed(JsonConvert.DeserializeObject<PasswordlessNotificationData>(notificationDataJson)).FireAndForget();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
_logger.Value.Exception(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
43
src/Android/Renderers/CustomLabelRenderer.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System.ComponentModel;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Bit.App.Controls;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(CustomLabel), typeof(CustomLabelRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomLabelRenderer : LabelRenderer
|
||||
{
|
||||
public CustomLabelRenderer(Context context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Label> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
if (Control != null && e.NewElement is CustomLabel label)
|
||||
{
|
||||
if (label.FontWeight.HasValue && Build.VERSION.SdkInt >= BuildVersionCodes.P)
|
||||
{
|
||||
Control.Typeface = Android.Graphics.Typeface.Create(null, label.FontWeight.Value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
|
||||
{
|
||||
var label = sender as CustomLabel;
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(CustomLabel.AutomationId):
|
||||
Control.ContentDescription = label.AutomationId;
|
||||
break;
|
||||
}
|
||||
base.OnElementPropertyChanged(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/Android/Renderers/CustomPageRenderer.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using AndroidX.AppCompat.Widget;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Droid.Renderers;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
[assembly: ExportRenderer(typeof(ContentPage), typeof(CustomPageRenderer))]
|
||||
namespace Bit.Droid.Renderers
|
||||
{
|
||||
public class CustomPageRenderer : PageRenderer
|
||||
{
|
||||
public CustomPageRenderer(Context context) : base(context)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
|
||||
{
|
||||
base.OnElementChanged(e);
|
||||
|
||||
Activity context = (Activity)this.Context;
|
||||
var toolbar = context.FindViewById<Toolbar>(Resource.Id.toolbar);
|
||||
if(toolbar != null)
|
||||
{
|
||||
toolbar.NavigationContentDescription = AppResources.TapToGoBack;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ namespace Bit.Droid.Renderers
|
||||
}
|
||||
if (Control != null)
|
||||
{
|
||||
Control.SetHintTextColor(ThemeHelpers.MutedColor);
|
||||
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.switch_thumb, null);
|
||||
if (t is GradientDrawable thumb)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/darkgray"/>
|
||||
<foreground android:drawable="@drawable/logo_rounded"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/logo_rounded"/>
|
||||
</adaptive-icon>
|
||||
41
src/Android/Resources/drawable/empty_login_requests.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="143"
|
||||
android:width="200dp"
|
||||
android:height="143dp">
|
||||
<path
|
||||
android:pathData="M34 43H10C6.68629 43 4 45.6863 4 49V109C4 112.314 6.68629 115 10 115H34C37.3137 115 40 112.314 40 109V49C40 45.6863 37.3137 43 34 43ZM10 39C4.47715 39 0 43.4772 0 49V109C0 114.523 4.47715 119 10 119H34C39.5228 119 44 114.523 44 109V49C44 43.4772 39.5228 39 34 39H10Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M20.3701 47.809C20.3701 47.2567 20.8178 46.809 21.3701 46.809H22.6122C23.1645 46.809 23.6122 47.2567 23.6122 47.809C23.6122 48.3612 23.1645 48.809 22.6122 48.809H21.3701C20.8178 48.809 20.3701 48.3612 20.3701 47.809Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M68 120C68 119.448 68.4477 119 69 119H127C127.552 119 128 119.448 128 120C128 120.552 127.552 121 127 121H69C68.4477 121 68 120.552 68 120Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M87.7402 120V102.236H89.7402V120H87.7402Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M107.71 120V102.236H109.71V120H107.71Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M27 25C27 17.268 33.268 11 41 11H157C164.732 11 171 17.268 171 25V31H167V25C167 19.4772 162.523 15 157 15H41C35.4772 15 31 19.4772 31 25V41H27V25ZM42 99H127V103H42V99Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M35 26C35 22.134 38.134 19 42 19H156C159.866 19 163 22.134 163 26V31H161V26C161 23.2386 158.761 21 156 21H42C39.2386 21 37 23.2386 37 26V41H35V26ZM42 93H127V95H42V93Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M125 39C125 33.4771 129.477 29 135 29H188C193.523 29 198 33.4772 198 39V119C198 124.523 193.523 129 188 129H135C129.477 129 125 124.523 125 119V39ZM135 33C131.686 33 129 35.6863 129 39V119C129 122.314 131.686 125 135 125H188C191.314 125 194 122.314 194 119V39C194 35.6863 191.314 33 188 33H135Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M164 120C164 121.105 163.105 122 162 122C160.895 122 160 121.105 160 120C160 118.895 160.895 118 162 118C163.105 118 164 118.895 164 120Z"
|
||||
android:fillColor="#89929F" />
|
||||
</vector>
|
||||
41
src/Android/Resources/drawable/empty_login_requests_dark.xml
Normal file
@@ -0,0 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:viewportWidth="200"
|
||||
android:viewportHeight="143"
|
||||
android:width="200dp"
|
||||
android:height="143dp">
|
||||
<path
|
||||
android:pathData="M34 43H10C6.68629 43 4 45.6863 4 49V109C4 112.314 6.68629 115 10 115H34C37.3137 115 40 112.314 40 109V49C40 45.6863 37.3137 43 34 43ZM10 39C4.47715 39 0 43.4772 0 49V109C0 114.523 4.47715 119 10 119H34C39.5228 119 44 114.523 44 109V49C44 43.4772 39.5228 39 34 39H10Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M20.3701 47.809C20.3701 47.2567 20.8178 46.809 21.3701 46.809H22.6122C23.1645 46.809 23.6122 47.2567 23.6122 47.809C23.6122 48.3612 23.1645 48.809 22.6122 48.809H21.3701C20.8178 48.809 20.3701 48.3612 20.3701 47.809Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M68 120C68 119.448 68.4477 119 69 119H127C127.552 119 128 119.448 128 120C128 120.552 127.552 121 127 121H69C68.4477 121 68 120.552 68 120Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M87.7402 120V102.236H89.7402V120H87.7402Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M107.71 120V102.236H109.71V120H107.71Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M27 25C27 17.268 33.268 11 41 11H157C164.732 11 171 17.268 171 25V31H167V25C167 19.4772 162.523 15 157 15H41C35.4772 15 31 19.4772 31 25V41H27V25ZM42 99H127V103H42V99Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M35 26C35 22.134 38.134 19 42 19H156C159.866 19 163 22.134 163 26V31H161V26C161 23.2386 158.761 21 156 21H42C39.2386 21 37 23.2386 37 26V41H35V26ZM42 93H127V95H42V93Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M125 39C125 33.4771 129.477 29 135 29H188C193.523 29 198 33.4772 198 39V119C198 124.523 193.523 129 188 129H135C129.477 129 125 124.523 125 119V39ZM135 33C131.686 33 129 35.6863 129 39V119C129 122.314 131.686 125 135 125H188C191.314 125 194 122.314 194 119V39C194 35.6863 191.314 33 188 33H135Z"
|
||||
android:fillType="evenOdd"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M164 120C164 121.105 163.105 122 162 122C160.895 122 160 121.105 160 120C160 118.895 160.895 118 162 118C163.105 118 164 118.895 164 120Z"
|
||||
android:fillColor="#A3A3A3" />
|
||||
</vector>
|
||||
35
src/Android/Resources/drawable/empty_uris_placeholder.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:viewportWidth="129"
|
||||
android:viewportHeight="124"
|
||||
android:width="129dp"
|
||||
android:height="124dp">
|
||||
<path
|
||||
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
||||
android:fillColor="#F0F0F0"
|
||||
android:strokeColor="#89929F"
|
||||
android:strokeWidth="3" />
|
||||
<path
|
||||
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
||||
android:strokeColor="#89929F"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
||||
android:strokeColor="#89929F"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
||||
android:strokeColor="#89929F"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
||||
android:fillColor="#89929F" />
|
||||
<path
|
||||
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
||||
android:fillColor="#89929F" />
|
||||
</vector>
|
||||
@@ -0,0 +1,35 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:viewportWidth="129"
|
||||
android:viewportHeight="124"
|
||||
android:width="129dp"
|
||||
android:height="124dp">
|
||||
<path
|
||||
android:pathData="M126.8227 61.9441A59.6843 59.6843 0 0 1 7.4541 61.9441A59.6843 59.6843 0 0 1 126.8227 61.9441Z"
|
||||
android:fillColor="@android:color/transparent"
|
||||
android:strokeColor="#A3A3A3"
|
||||
android:strokeWidth="3" />
|
||||
<path
|
||||
android:pathData="M21.6167 100.851C52.597 103.31 79.6937 80.3264 82.1391 49.5156C83.6205 30.8497 76.0789 14.8844 62.7275 3.63385"
|
||||
android:strokeColor="#A3A3A3"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M14.5633 34.2845C12.2035 66.7711 38.5225 96.3429 72.6666 98.8232C74.2596 98.9389 78.629 98.9975 80.1951 99C84.6245 98.8232 97.8063 96.593 106.813 91.8485C113.439 88.3581 119.745 84.6984 124.644 79.1121"
|
||||
android:strokeColor="#A3A3A3"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M124.502 48.5051C106.554 24.3817 68.8237 21.6709 41.4178 42.0617C24.8146 54.4149 14.7327 72.4183 13.9255 90.1427"
|
||||
android:strokeColor="#A3A3A3"
|
||||
android:strokeWidth="1.5"
|
||||
android:strokeLineCap="round" />
|
||||
<path
|
||||
android:pathData="M83.4034 28.3934A5 5 0 0 1 73.4034 28.3934A5 5 0 0 1 83.4034 28.3934Z"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M24.7698 66.5518A5 5 0 0 1 14.7698 66.5518A5 5 0 0 1 24.7698 66.5518Z"
|
||||
android:fillColor="#A3A3A3" />
|
||||
<path
|
||||
android:pathData="M57.344 94.4726A5 5 0 0 1 47.344 94.4726A5 5 0 0 1 57.344 94.4726Z"
|
||||
android:fillColor="#A3A3A3" />
|
||||
</vector>
|
||||
15
src/Android/Resources/drawable/ic_launcher_monochrome.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="0.099"
|
||||
android:scaleY="0.099"
|
||||
android:translateX="24.3"
|
||||
android:translateY="24.3">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M481.4,102.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6L131.7,96.6c-5.1,0 -9.4,1.9 -13.1,5.6C114.9,105.9 113,110.2 113,115.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8L487.3,115.3C487,110.2 485.1,105.9 481.4,102.2zM438,341.8C438,423 300,493 300,493L300,144.6h138C438,144.6 438,260.6 438,341.8z" />
|
||||
</group>
|
||||
</vector>
|
||||
4
src/Android/Resources/drawable/ic_notification.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<vector android:height="24dp" android:viewportHeight="420"
|
||||
android:viewportWidth="420" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FFFFFF" android:pathData="M350.43,40.516C347.563,37.65 344.153,36.178 340.281,36.178L79.487,36.178C75.538,36.178 72.206,37.65 69.338,40.516C66.472,43.384 65,46.716 65,50.665L65,224.527C65,237.466 67.557,250.405 72.593,263.112C77.629,275.895 83.904,287.207 91.42,297.046C98.857,306.964 107.768,316.571 118.149,325.869C128.455,335.242 138.063,342.99 146.817,349.19C155.573,355.387 164.715,361.198 174.243,366.699C183.773,372.2 190.514,375.919 194.543,377.933C198.572,379.871 201.749,381.421 204.151,382.426C205.932,383.357 207.948,383.821 210.04,383.821C212.131,383.821 214.145,383.357 215.929,382.426C218.329,381.344 221.584,379.871 225.534,377.933C229.563,375.997 236.304,372.2 245.832,366.699C255.365,361.198 264.506,355.311 273.262,349.19C282.017,342.99 291.545,335.242 301.928,325.869C312.232,316.493 321.142,306.886 328.657,297.046C336.096,287.129 342.372,275.819 347.407,263.112C352.444,250.328 355,237.466 355,224.527L355,50.665C354.768,46.716 353.296,43.384 350.43,40.516ZM316.804,226.154C316.804,289.067 209.883,343.302 209.883,343.302L209.883,73.368L316.804,73.368C316.804,73.368 316.804,163.242 316.804,226.154Z"/>
|
||||
</vector>
|
||||
14
src/Android/Resources/drawable/logo_rounded.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.11454546"
|
||||
android:scaleY="0.11454546"
|
||||
android:translateX="31.663637"
|
||||
android:translateY="27.54">
|
||||
<path
|
||||
android:pathData="M376.4,12.2c-3.7,-3.7 -8.1,-5.6 -13.1,-5.6H26.7c-5.1,0 -9.4,1.9 -13.1,5.6C9.9,15.9 8,20.2 8,25.3v224.4c0,16.7 3.3,33.4 9.8,49.8c6.5,16.5 14.6,31.1 24.3,43.8c9.6,12.8 21.1,25.2 34.5,37.2c13.3,12.1 25.7,22.1 37,30.1c11.3,8 23.1,15.5 35.4,22.6c12.3,7.1 21,11.9 26.2,14.5c5.2,2.5 9.3,4.5 12.4,5.8c2.3,1.2 4.9,1.8 7.6,1.8c2.7,0 5.3,-0.6 7.6,-1.8c3.1,-1.4 7.3,-3.3 12.4,-5.8c5.2,-2.5 13.9,-7.4 26.2,-14.5c12.3,-7.1 24.1,-14.7 35.4,-22.6c11.3,-8 23.6,-18 37,-30.1c13.3,-12.1 24.8,-24.5 34.5,-37.2c9.6,-12.8 17.7,-27.4 24.2,-43.8c6.5,-16.5 9.8,-33.1 9.8,-49.8V25.3C382,20.2 380.1,15.9 376.4,12.2zM333,251.8C333,333 195,403 195,403V54.6h138C333,54.6 333,170.6 333,251.8z"
|
||||
android:fillColor="#FFFFFF"/>
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="30dp"
|
||||
android:paddingRight="30dp">
|
||||
<TextView
|
||||
android:id="@+id/lblHeader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/dialog_header_text_size"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="-3dp"
|
||||
android:labelFor="@+id/txtValue"/>
|
||||
<EditText
|
||||
android:id="@id/txtValue"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/dialog_input_text_size"/>
|
||||
<TextView
|
||||
android:id="@+id/lblValueSubinfo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/dialog_sub_value_info_text_size"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -4,6 +4,7 @@
|
||||
<style name="LaunchTheme" parent="BaseTheme">
|
||||
<item name="android:windowBackground">@drawable/splash_screen_dark</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
||||
</style>
|
||||
|
||||
<style name="BaseTheme" parent="Theme.AppCompat">
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<dimen name="design_bottom_navigation_text_size" tools:override="true">15sp</dimen>
|
||||
<dimen name="design_bottom_navigation_active_text_size" tools:override="true">15sp</dimen>
|
||||
<dimen name="dialog_input_text_size">16sp</dimen>
|
||||
<dimen name="dialog_header_text_size">12sp</dimen>
|
||||
<dimen name="dialog_sub_value_info_text_size">12sp</dimen>
|
||||
</resources>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<style name="LaunchTheme" parent="BaseTheme">
|
||||
<item name="android:windowBackground">@drawable/splash_screen</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/splash_screen_round</item>
|
||||
</style>
|
||||
|
||||
<style name="BaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||
android:notificationTimeout="100"
|
||||
android:canRetrieveWindowContent="true"/>
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:isAccessibilityTool="false"/>
|
||||
@@ -17,6 +17,9 @@
|
||||
<compatibility-package
|
||||
android:name="alook.browser.google"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="app.vanadium.browser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.amazon.cloud9"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -77,6 +80,9 @@
|
||||
<compatibility-package
|
||||
android:name="com.google.android.captiveportallogin"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.iode.firefox"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.jamal2367.styx"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -86,6 +92,9 @@
|
||||
<compatibility-package
|
||||
android:name="com.kiwibrowser.browser.dev"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.lemurbrowser.exts"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.microsoft.emmx"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -110,12 +119,18 @@
|
||||
<compatibility-package
|
||||
android:name="com.naver.whale"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.neeva.app"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.opera.browser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.opera.browser.beta"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.opera.gx"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.opera.mini.native"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -131,6 +146,9 @@
|
||||
<compatibility-package
|
||||
android:name="com.qwant.liberty"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.rainsee.create"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.sec.android.app.sbrowser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -155,6 +173,15 @@
|
||||
<compatibility-package
|
||||
android:name="com.yandex.browser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.yjllq.internet"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.yjllq.kito"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.yujian.ResideMenuDemo"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="com.z28j.feel"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -176,6 +203,9 @@
|
||||
<compatibility-package
|
||||
android:name="mark.via.gp"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="net.dezor.browser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="net.slions.fulguris.full.download"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
@@ -206,6 +236,9 @@
|
||||
<compatibility-package
|
||||
android:name="org.codeaurora.swe.browser"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="org.cromite.cromite"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
<compatibility-package
|
||||
android:name="org.gnu.icecat"
|
||||
android:maxLongVersionCode="10000000000"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<cache-path name="cache" path="." />
|
||||
<files-path name="internal" path="." />
|
||||
<files-path name="temp_camera_images" path="camera_temp/" />
|
||||
</paths>
|
||||
|
||||
@@ -1,10 +1,21 @@
|
||||
#if !FDROID
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using AndroidX.Core.App;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Droid.Receivers;
|
||||
using Bit.Droid.Utilities;
|
||||
using Newtonsoft.Json;
|
||||
using Xamarin.Forms;
|
||||
using static Xamarin.Essentials.Platform;
|
||||
using Intent = Android.Content.Intent;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
@@ -23,6 +34,11 @@ namespace Bit.Droid.Services
|
||||
|
||||
public bool IsRegisteredForPush => NotificationManagerCompat.From(Android.App.Application.Context)?.AreNotificationsEnabled() ?? false;
|
||||
|
||||
public Task<bool> AreNotificationsSettingsEnabledAsync()
|
||||
{
|
||||
return Task.FromResult(IsRegisteredForPush);
|
||||
}
|
||||
|
||||
public async Task<string> GetTokenAsync()
|
||||
{
|
||||
return await _stateService.GetPushCurrentTokenAsync();
|
||||
@@ -47,6 +63,50 @@ namespace Bit.Droid.Services
|
||||
// Do we ever need to unregister?
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public void DismissLocalNotification(string notificationId)
|
||||
{
|
||||
if (int.TryParse(notificationId, out int intNotificationId))
|
||||
{
|
||||
var notificationManager = NotificationManagerCompat.From(Android.App.Application.Context);
|
||||
notificationManager.Cancel(intNotificationId);
|
||||
}
|
||||
}
|
||||
|
||||
public void SendLocalNotification(string title, string message, BaseNotificationData data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(data.Id))
|
||||
{
|
||||
throw new ArgumentNullException("notificationId cannot be null or empty.");
|
||||
}
|
||||
|
||||
var context = Android.App.Application.Context;
|
||||
var intent = new Intent(context, typeof(MainActivity));
|
||||
intent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
||||
var pendingIntentFlags = AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, true);
|
||||
var pendingIntent = PendingIntent.GetActivity(context, 20220801, intent, pendingIntentFlags);
|
||||
|
||||
var deleteIntent = new Intent(context, typeof(NotificationDismissReceiver));
|
||||
deleteIntent.PutExtra(Bit.Core.Constants.NotificationData, JsonConvert.SerializeObject(data));
|
||||
var deletePendingIntent = PendingIntent.GetBroadcast(context, 20220802, deleteIntent, pendingIntentFlags);
|
||||
|
||||
var builder = new NotificationCompat.Builder(context, Bit.Core.Constants.AndroidNotificationChannelId)
|
||||
.SetContentIntent(pendingIntent)
|
||||
.SetContentTitle(title)
|
||||
.SetContentText(message)
|
||||
.SetSmallIcon(Resource.Drawable.ic_notification)
|
||||
.SetColor((int)Android.Graphics.Color.White)
|
||||
.SetDeleteIntent(deletePendingIntent)
|
||||
.SetAutoCancel(true);
|
||||
|
||||
if (data is PasswordlessNotificationData passwordlessNotificationData && passwordlessNotificationData.TimeoutInMinutes > 0)
|
||||
{
|
||||
builder.SetTimeoutAfter(passwordlessNotificationData.TimeoutInMinutes * 60000);
|
||||
}
|
||||
|
||||
var notificationManager = NotificationManagerCompat.From(context);
|
||||
notificationManager.Notify(int.Parse(data.Id), builder.Build());
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
215
src/Android/Services/AutofillHandler.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.App.Assist;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Views.Autofill;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Autofill;
|
||||
using Plugin.CurrentActivity;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class AutofillHandler : IAutofillHandler
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IClipboardService _clipboardService;
|
||||
private readonly IPlatformUtilsService _platformUtilsService;
|
||||
private readonly LazyResolve<IEventService> _eventService;
|
||||
|
||||
public AutofillHandler(IStateService stateService,
|
||||
IMessagingService messagingService,
|
||||
IClipboardService clipboardService,
|
||||
IPlatformUtilsService platformUtilsService,
|
||||
LazyResolve<IEventService> eventService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_messagingService = messagingService;
|
||||
_clipboardService = clipboardService;
|
||||
_platformUtilsService = platformUtilsService;
|
||||
_eventService = eventService;
|
||||
}
|
||||
|
||||
public bool AutofillServiceEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var afm = (AutofillManager)activity.GetSystemService(
|
||||
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
||||
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsAutofillService()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||
return manager.IsAutofillSupported;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Autofill(CipherView cipher)
|
||||
{
|
||||
var activity = CrossCurrentActivity.Current.Activity as Xamarin.Forms.Platform.Android.FormsAppCompatActivity;
|
||||
if (activity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (activity.Intent?.GetBooleanExtra(AutofillConstants.AutofillFramework, false) ?? false)
|
||||
{
|
||||
if (cipher == null)
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var structure = activity.Intent.GetParcelableExtra(
|
||||
AutofillManager.ExtraAssistStructure) as AssistStructure;
|
||||
if (structure == null)
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var parser = new Parser(structure, activity.ApplicationContext);
|
||||
parser.Parse();
|
||||
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var task = CopyTotpAsync(cipher);
|
||||
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher), false);
|
||||
var replyIntent = new Intent();
|
||||
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
||||
activity.SetResult(Result.Ok, replyIntent);
|
||||
activity.Finish();
|
||||
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = new Intent();
|
||||
if (cipher?.Login == null)
|
||||
{
|
||||
data.PutExtra("canceled", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
var task = CopyTotpAsync(cipher);
|
||||
data.PutExtra("uri", cipher.Login.Uri);
|
||||
data.PutExtra("username", cipher.Login.Username);
|
||||
data.PutExtra("password", cipher.Login.Password);
|
||||
}
|
||||
if (activity.Parent == null)
|
||||
{
|
||||
activity.SetResult(Result.Ok, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.Parent.SetResult(Result.Ok, data);
|
||||
}
|
||||
activity.Finish();
|
||||
_messagingService.Send("finishMainActivity");
|
||||
if (cipher != null)
|
||||
{
|
||||
var eventTask = _eventService.Value.CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseAutofill()
|
||||
{
|
||||
Autofill(null);
|
||||
}
|
||||
|
||||
public bool AutofillAccessibilityServiceRunning()
|
||||
{
|
||||
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
|
||||
Settings.Secure.EnabledAccessibilityServices);
|
||||
return Application.Context.PackageName != null &&
|
||||
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
|
||||
}
|
||||
|
||||
public bool AutofillAccessibilityOverlayPermitted()
|
||||
{
|
||||
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void DisableAutofillService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||
manager.DisableAutofillServices();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public bool AutofillServicesEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||
{
|
||||
// Android 5-6: Both accessibility & overlay are required or nothing happens
|
||||
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
|
||||
}
|
||||
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
|
||||
{
|
||||
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
|
||||
return AutofillAccessibilityServiceRunning();
|
||||
}
|
||||
// Android 8+: Either autofill or accessibility is required
|
||||
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
|
||||
}
|
||||
|
||||
private async Task CopyTotpAsync(CipherView cipher)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
|
||||
{
|
||||
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (totp != null)
|
||||
{
|
||||
await _clipboardService.CopyTextAsync(totp);
|
||||
_platformUtilsService.ShowToastForCopiedValue(AppResources.VerificationCodeTotp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,15 @@
|
||||
using System.Threading.Tasks;
|
||||
using Android.OS;
|
||||
using Android.Security.Keystore;
|
||||
using Bit.App.Services;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Services;
|
||||
using Java.Security;
|
||||
using Javax.Crypto;
|
||||
#if !FDROID
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
#endif
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class BiometricService : IBiometricService
|
||||
public class BiometricService : BaseBiometricService
|
||||
{
|
||||
private const string KeyName = "com.8bit.bitwarden.biometric_integrity";
|
||||
|
||||
@@ -24,28 +23,28 @@ namespace Bit.Droid.Services
|
||||
|
||||
private readonly KeyStore _keystore;
|
||||
|
||||
public BiometricService()
|
||||
public BiometricService(IStateService stateService, ICryptoService cryptoService)
|
||||
: base(stateService, cryptoService)
|
||||
{
|
||||
_keystore = KeyStore.GetInstance(KeyStoreName);
|
||||
_keystore.Load(null);
|
||||
}
|
||||
|
||||
public Task<bool> SetupBiometricAsync(string bioIntegrityKey = null)
|
||||
public override async Task<bool> SetupBiometricAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
// bioIntegrityKey used in iOS only
|
||||
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
CreateKey();
|
||||
await CreateKeyAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public Task<bool> ValidateIntegrityAsync(string bioIntegrityKey = null)
|
||||
public override async Task<bool> IsSystemBiometricIntegrityValidAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.M)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
@@ -56,7 +55,7 @@ namespace Bit.Droid.Services
|
||||
|
||||
if (key == null || cipher == null)
|
||||
{
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
cipher.Init(CipherMode.EncryptMode, key);
|
||||
@@ -64,27 +63,32 @@ namespace Bit.Droid.Services
|
||||
catch (KeyPermanentlyInvalidatedException e)
|
||||
{
|
||||
// Biometric has changed
|
||||
return Task.FromResult(false);
|
||||
await ClearStateAsync(bioIntegritySrcKey);
|
||||
return false;
|
||||
}
|
||||
catch (UnrecoverableKeyException e)
|
||||
{
|
||||
// Biometric was disabled and re-enabled
|
||||
return Task.FromResult(false);
|
||||
await ClearStateAsync(bioIntegritySrcKey);
|
||||
return false;
|
||||
}
|
||||
catch (InvalidKeyException e)
|
||||
{
|
||||
// Fallback for old bitwarden users without a key
|
||||
#if !FDROID
|
||||
Crashes.TrackError(e);
|
||||
#endif
|
||||
CreateKey();
|
||||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
await CreateKeyAsync(bioIntegritySrcKey);
|
||||
}
|
||||
|
||||
return Task.FromResult(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CreateKey()
|
||||
private async Task CreateKeyAsync(string bioIntegritySrcKey = null)
|
||||
{
|
||||
bioIntegritySrcKey ??= Core.Constants.BiometricIntegritySourceKey;
|
||||
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey,
|
||||
await GetStateAsync(bioIntegritySrcKey));
|
||||
await _stateService.SetAccountBiometricIntegrityValidAsync(bioIntegritySrcKey);
|
||||
|
||||
try
|
||||
{
|
||||
var keyGen = KeyGenerator.GetInstance(KeyAlgorithm, KeyStoreName);
|
||||
@@ -101,10 +105,19 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
// Catch silently to allow biometrics to function on devices that are in a state where key generation
|
||||
// is not functioning
|
||||
#if !FDROID
|
||||
Crashes.TrackError(e);
|
||||
#endif
|
||||
LoggerHelper.LogEvenIfCantBeResolved(e);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> GetStateAsync(string bioIntegritySrcKey)
|
||||
{
|
||||
return await _stateService.GetSystemBiometricIntegrityState(bioIntegritySrcKey) ??
|
||||
Guid.NewGuid().ToString();
|
||||
}
|
||||
|
||||
private async Task ClearStateAsync(string bioIntegritySrcKey)
|
||||
{
|
||||
await _stateService.SetSystemBiometricIntegrityState(bioIntegritySrcKey, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Bit.Core;
|
||||
using Android.OS;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Droid.Receivers;
|
||||
using Plugin.CurrentActivity;
|
||||
using Bit.Droid.Utilities;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
@@ -20,17 +20,45 @@ namespace Bit.Droid.Services
|
||||
_stateService = stateService;
|
||||
|
||||
_clearClipboardPendingIntent = new Lazy<PendingIntent>(() =>
|
||||
PendingIntent.GetBroadcast(CrossCurrentActivity.Current.Activity,
|
||||
PendingIntent.GetBroadcast(Application.Context,
|
||||
0,
|
||||
new Intent(CrossCurrentActivity.Current.Activity, typeof(ClearClipboardAlarmReceiver)),
|
||||
PendingIntentFlags.UpdateCurrent));
|
||||
new Intent(Application.Context, typeof(ClearClipboardAlarmReceiver)),
|
||||
AndroidHelpers.AddPendingIntentMutabilityFlag(PendingIntentFlags.UpdateCurrent, false)));
|
||||
}
|
||||
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1)
|
||||
public async Task CopyTextAsync(string text, int expiresInMs = -1, bool isSensitive = true)
|
||||
{
|
||||
await Clipboard.SetTextAsync(text);
|
||||
try
|
||||
{
|
||||
// Xamarin.Essentials.Clipboard currently doesn't support the IS_SENSITIVE flag for API 33+
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
{
|
||||
await Clipboard.SetTextAsync(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
CopyToClipboard(text, isSensitive);
|
||||
}
|
||||
|
||||
await ClearClipboardAlarmAsync(expiresInMs);
|
||||
await ClearClipboardAlarmAsync(expiresInMs);
|
||||
}
|
||||
catch (Java.Lang.SecurityException ex) when (ex.Message.Contains("does not belong to"))
|
||||
{
|
||||
// #1962 Just ignore, the content is copied either way but there is some app interfering in the process
|
||||
// that the OS catches and just throws this exception.
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToClipboard(string text, bool isSensitive = true)
|
||||
{
|
||||
var clipboardManager = Application.Context.GetSystemService(Context.ClipboardService) as ClipboardManager;
|
||||
var clipData = ClipData.NewPlainText("bitwarden", text);
|
||||
if (isSensitive)
|
||||
{
|
||||
clipData.Description.Extras ??= new PersistableBundle();
|
||||
clipData.Description.Extras.PutBoolean("android.content.extra.IS_SENSITIVE", true);
|
||||
}
|
||||
clipboardManager.PrimaryClip = clipData;
|
||||
}
|
||||
|
||||
private async Task ClearClipboardAlarmAsync(int expiresInMs = -1)
|
||||
@@ -50,7 +78,7 @@ namespace Bit.Droid.Services
|
||||
return;
|
||||
}
|
||||
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs;
|
||||
var alarmManager = CrossCurrentActivity.Current.Activity.GetSystemService(Context.AlarmService) as AlarmManager;
|
||||
var alarmManager = Application.Context.GetSystemService(Context.AlarmService) as AlarmManager;
|
||||
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ using Org.BouncyCastle.Crypto.Digests;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Parameters;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Java.Lang;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
@@ -33,5 +35,19 @@ namespace Bit.Droid.Services
|
||||
generator.Init(password, salt, iterations);
|
||||
return ((KeyParameter)generator.GenerateDerivedMacParameters(keySize)).GetKey();
|
||||
}
|
||||
|
||||
public byte[] Argon2id(byte[] password, byte[] salt, int iterations, int memory, int parallelism)
|
||||
{
|
||||
JavaSystem.LoadLibrary("argon2");
|
||||
int keySize = 32;
|
||||
var key = new byte[keySize];
|
||||
argon2id_hash_raw(iterations, memory, parallelism,
|
||||
password, password.Length, salt, salt.Length, key, key.Length);
|
||||
return key;
|
||||
}
|
||||
|
||||
[DllImport("argon2", EntryPoint = "argon2id_hash_raw")]
|
||||
private static extern int argon2id_hash_raw(int timeCost, int memoryCost, int parallelism,
|
||||
byte[] pwd, int pwdlen, byte[] salt, int saltlen, byte[] hash, int hashlen);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Android;
|
||||
using Android.App;
|
||||
using Android.App.Assist;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.Nfc;
|
||||
@@ -14,22 +9,17 @@ using Android.Provider;
|
||||
using Android.Text;
|
||||
using Android.Text.Method;
|
||||
using Android.Views;
|
||||
using Android.Views.Autofill;
|
||||
using Android.Views.InputMethods;
|
||||
using Android.Webkit;
|
||||
using Android.Widget;
|
||||
using AndroidX.Core.App;
|
||||
using AndroidX.Core.Content;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.App.Utilities;
|
||||
using Bit.App.Utilities.Prompts;
|
||||
using Bit.Core.Abstractions;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.View;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Droid.Autofill;
|
||||
using Bit.Droid.Utilities;
|
||||
using Plugin.CurrentActivity;
|
||||
using Xamarin.Forms.Platform.Android;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
@@ -37,33 +27,18 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IMessagingService _messagingService;
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
private readonly Func<IEventService> _eventServiceFunc;
|
||||
private AlertDialog _progressDialog;
|
||||
object _progressDialogLock = new object();
|
||||
|
||||
private bool _cameraPermissionsDenied;
|
||||
private Toast _toast;
|
||||
private string _userAgent;
|
||||
|
||||
public DeviceActionService(
|
||||
IStateService stateService,
|
||||
IMessagingService messagingService,
|
||||
IBroadcasterService broadcasterService,
|
||||
Func<IEventService> eventServiceFunc)
|
||||
IMessagingService messagingService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_messagingService = messagingService;
|
||||
_broadcasterService = broadcasterService;
|
||||
_eventServiceFunc = eventServiceFunc;
|
||||
|
||||
_broadcasterService.Subscribe(nameof(DeviceActionService), (message) =>
|
||||
{
|
||||
if (message.Command == "selectFileCameraPermissionDenied")
|
||||
{
|
||||
_cameraPermissionsDenied = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public string DeviceUserAgent
|
||||
@@ -96,14 +71,17 @@ namespace Bit.Droid.Services
|
||||
|
||||
public bool LaunchApp(string appName)
|
||||
{
|
||||
if ((int)Build.VERSION.SdkInt < 33)
|
||||
{
|
||||
// API 33 required to avoid using wildcard app visibility or dangerous permissions
|
||||
// https://developer.android.com/reference/android/content/pm/PackageManager#getLaunchIntentSenderForPackage(java.lang.String)
|
||||
return false;
|
||||
}
|
||||
var activity = CrossCurrentActivity.Current.Activity;
|
||||
appName = appName.Replace("androidapp://", string.Empty);
|
||||
var launchIntent = activity.PackageManager.GetLaunchIntentForPackage(appName);
|
||||
if (launchIntent != null)
|
||||
{
|
||||
activity.StartActivity(launchIntent);
|
||||
}
|
||||
return launchIntent != null;
|
||||
var launchIntentSender = activity?.PackageManager?.GetLaunchIntentSenderForPackage(appName);
|
||||
launchIntentSender?.SendIntent(activity, Result.Ok, null, null, null);
|
||||
return launchIntentSender != null;
|
||||
}
|
||||
|
||||
public async Task ShowLoadingAsync(string text)
|
||||
@@ -209,184 +187,6 @@ namespace Bit.Droid.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool OpenFile(byte[] fileData, string id, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var intent = BuildOpenFileIntent(fileData, fileName);
|
||||
if (intent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
activity.StartActivity(intent);
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanOpenFile(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
||||
if (intent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var activities = activity.PackageManager.QueryIntentActivities(intent,
|
||||
PackageInfoFlags.MatchDefaultOnly);
|
||||
return (activities?.Count ?? 0) > 0;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
|
||||
{
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if (extension == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if (mimeType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var cachePath = activity.CacheDir;
|
||||
var filePath = Path.Combine(cachePath.Path, fileName);
|
||||
File.WriteAllBytes(filePath, fileData);
|
||||
var file = new Java.IO.File(cachePath, fileName);
|
||||
if (!file.IsFile)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var intent = new Intent(Intent.ActionView);
|
||||
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
|
||||
"com.x8bit.bitwarden.fileprovider", file);
|
||||
intent.SetDataAndType(uri, mimeType);
|
||||
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
||||
return intent;
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
|
||||
if (contentUri != null)
|
||||
{
|
||||
var uri = Android.Net.Uri.Parse(contentUri);
|
||||
var stream = activity.ContentResolver.OpenOutputStream(uri);
|
||||
// Using java bufferedOutputStream due to this issue:
|
||||
// https://github.com/xamarin/xamarin-android/issues/3498
|
||||
var javaStream = new Java.IO.BufferedOutputStream(stream);
|
||||
javaStream.Write(fileData);
|
||||
javaStream.Flush();
|
||||
javaStream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prompt for location to save file
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if (extension == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if (mimeType == null)
|
||||
{
|
||||
// Unable to identify so fall back to generic "any" type
|
||||
mimeType = "*/*";
|
||||
}
|
||||
|
||||
var intent = new Intent(Intent.ActionCreateDocument);
|
||||
intent.SetType(mimeType);
|
||||
intent.AddCategory(Intent.CategoryOpenable);
|
||||
intent.PutExtra(Intent.ExtraTitle, fileName);
|
||||
|
||||
activity.StartActivityForResult(intent, Constants.SaveFileRequestCode);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
||||
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
public Task SelectFileAsync()
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
||||
HasPermission(Manifest.Permission.WriteExternalStorage);
|
||||
var additionalIntents = new List<IParcelable>();
|
||||
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
|
||||
{
|
||||
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
|
||||
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
|
||||
{
|
||||
AskPermission(Manifest.Permission.WriteExternalStorage);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
if (!_cameraPermissionsDenied && !hasCameraPermission)
|
||||
{
|
||||
AskPermission(Manifest.Permission.Camera);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = new Java.IO.File(activity.FilesDir, "temp_camera_photo.jpg");
|
||||
if (!file.Exists())
|
||||
{
|
||||
file.ParentFile.Mkdirs();
|
||||
file.CreateNewFile();
|
||||
}
|
||||
var outputFileUri = FileProvider.GetUriForFile(activity,
|
||||
"com.x8bit.bitwarden.fileprovider", file);
|
||||
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
|
||||
}
|
||||
catch (Java.IO.IOException) { }
|
||||
}
|
||||
}
|
||||
|
||||
var docIntent = new Intent(Intent.ActionOpenDocument);
|
||||
docIntent.AddCategory(Intent.CategoryOpenable);
|
||||
docIntent.SetType("*/*");
|
||||
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
|
||||
if (additionalIntents.Count > 0)
|
||||
{
|
||||
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
|
||||
}
|
||||
activity.StartActivityForResult(chooserIntent, Constants.SelectFileRequestCode);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
public Task<string> DisplayPromptAync(string title = null, string description = null,
|
||||
string text = null, string okButtonText = null, string cancelButtonText = null,
|
||||
bool numericKeyboard = false, bool autofocus = true, bool password = false)
|
||||
@@ -410,10 +210,7 @@ namespace Bit.Droid.Services
|
||||
}
|
||||
if (numericKeyboard)
|
||||
{
|
||||
input.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
input.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
SetNumericKeyboardTo(input);
|
||||
}
|
||||
if (password)
|
||||
{
|
||||
@@ -449,6 +246,83 @@ namespace Bit.Droid.Services
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
public Task<ValidatablePromptResponse?> DisplayValidatablePromptAsync(ValidatablePromptConfig config)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
if (activity == null)
|
||||
{
|
||||
return Task.FromResult<ValidatablePromptResponse?>(null);
|
||||
}
|
||||
|
||||
var alertBuilder = new AlertDialog.Builder(activity);
|
||||
alertBuilder.SetTitle(config.Title);
|
||||
var view = activity.LayoutInflater.Inflate(Resource.Layout.validatable_input_dialog_layout, null);
|
||||
alertBuilder.SetView(view);
|
||||
|
||||
var result = new TaskCompletionSource<ValidatablePromptResponse?>();
|
||||
|
||||
alertBuilder.SetOnCancelListener(new BasicDialogWithResultCancelListener(result));
|
||||
alertBuilder.SetPositiveButton(config.OkButtonText ?? AppResources.Ok, listener: null);
|
||||
alertBuilder.SetNegativeButton(config.CancelButtonText ?? AppResources.Cancel, (sender, args) => result.TrySetResult(null));
|
||||
if (!string.IsNullOrEmpty(config.ThirdButtonText))
|
||||
{
|
||||
alertBuilder.SetNeutralButton(config.ThirdButtonText, (sender, args) => result.TrySetResult(new ValidatablePromptResponse(null, true)));
|
||||
}
|
||||
|
||||
var alert = alertBuilder.Create();
|
||||
|
||||
var input = view.FindViewById<EditText>(Resource.Id.txtValue);
|
||||
var lblHeader = view.FindViewById<TextView>(Resource.Id.lblHeader);
|
||||
var lblValueSubinfo = view.FindViewById<TextView>(Resource.Id.lblValueSubinfo);
|
||||
|
||||
lblHeader.Text = config.Subtitle;
|
||||
lblValueSubinfo.Text = config.ValueSubInfo;
|
||||
|
||||
var defaultSubInfoColor = lblValueSubinfo.TextColors;
|
||||
|
||||
input.InputType = InputTypes.ClassText;
|
||||
|
||||
if (config.NumericKeyboard)
|
||||
{
|
||||
SetNumericKeyboardTo(input);
|
||||
}
|
||||
|
||||
input.ImeOptions = input.ImeOptions | (ImeAction)ImeFlags.NoPersonalizedLearning | (ImeAction)ImeFlags.NoExtractUi;
|
||||
input.Text = config.Text ?? string.Empty;
|
||||
input.SetSelection(config.Text?.Length ?? 0);
|
||||
input.AfterTextChanged += (sender, args) =>
|
||||
{
|
||||
if (lblValueSubinfo.Text != config.ValueSubInfo)
|
||||
{
|
||||
lblValueSubinfo.Text = config.ValueSubInfo;
|
||||
lblHeader.SetTextColor(defaultSubInfoColor);
|
||||
lblValueSubinfo.SetTextColor(defaultSubInfoColor);
|
||||
}
|
||||
};
|
||||
|
||||
alert.Window.SetSoftInputMode(SoftInput.StateVisible);
|
||||
alert.Show();
|
||||
|
||||
var positiveButton = alert.GetButton((int)DialogButtonType.Positive);
|
||||
positiveButton.Click += (sender, args) =>
|
||||
{
|
||||
var error = config.ValidateText(input.Text);
|
||||
if (error != null)
|
||||
{
|
||||
lblHeader.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
||||
lblValueSubinfo.SetTextColor(ThemeManager.GetResourceColor("DangerColor").ToAndroid());
|
||||
lblValueSubinfo.Text = error;
|
||||
lblValueSubinfo.SendAccessibilityEvent(Android.Views.Accessibility.EventTypes.ViewFocused);
|
||||
return;
|
||||
}
|
||||
|
||||
result.TrySetResult(new ValidatablePromptResponse(input.Text, false));
|
||||
alert.Dismiss();
|
||||
};
|
||||
|
||||
return result.Task;
|
||||
}
|
||||
|
||||
public void RateApp()
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
@@ -464,34 +338,6 @@ namespace Bit.Droid.Services
|
||||
}
|
||||
}
|
||||
|
||||
public void DisableAutofillService()
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||
manager.DisableAutofillServices();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public bool AutofillServicesEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||
{
|
||||
// Android 5-6: Both accessibility & overlay are required or nothing happens
|
||||
return AutofillAccessibilityServiceRunning() && AutofillAccessibilityOverlayPermitted();
|
||||
}
|
||||
if (Build.VERSION.SdkInt == BuildVersionCodes.N)
|
||||
{
|
||||
// Android 7: Only accessibility is required (overlay is optional when using quick-action tile)
|
||||
return AutofillAccessibilityServiceRunning();
|
||||
}
|
||||
// Android 8+: Either autofill or accessibility is required
|
||||
return AutofillServiceEnabled() || AutofillAccessibilityServiceRunning();
|
||||
}
|
||||
|
||||
public string GetBuildNumber()
|
||||
{
|
||||
return Application.Context.ApplicationContext.PackageManager.GetPackageInfo(
|
||||
@@ -523,25 +369,6 @@ namespace Bit.Droid.Services
|
||||
return activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera);
|
||||
}
|
||||
|
||||
public bool SupportsAutofillService()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var type = Java.Lang.Class.FromType(typeof(AutofillManager));
|
||||
var manager = activity.GetSystemService(type) as AutofillManager;
|
||||
return manager.IsAutofillSupported;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public int SystemMajorVersion()
|
||||
{
|
||||
return (int)Build.VERSION.SdkInt;
|
||||
@@ -632,112 +459,6 @@ namespace Bit.Droid.Services
|
||||
title, cancel, destruction, buttons);
|
||||
}
|
||||
|
||||
public void Autofill(CipherView cipher)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
if (activity == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
||||
{
|
||||
if (cipher == null)
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var structure = activity.Intent.GetParcelableExtra(
|
||||
AutofillManager.ExtraAssistStructure) as AssistStructure;
|
||||
if (structure == null)
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var parser = new Parser(structure, activity.ApplicationContext);
|
||||
parser.Parse();
|
||||
if ((!parser.FieldCollection?.Fields?.Any() ?? true) || string.IsNullOrWhiteSpace(parser.Uri))
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
return;
|
||||
}
|
||||
var task = CopyTotpAsync(cipher);
|
||||
var dataset = AutofillHelpers.BuildDataset(activity, parser.FieldCollection, new FilledItem(cipher));
|
||||
var replyIntent = new Intent();
|
||||
replyIntent.PutExtra(AutofillManager.ExtraAuthenticationResult, dataset);
|
||||
activity.SetResult(Result.Ok, replyIntent);
|
||||
activity.Finish();
|
||||
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = new Intent();
|
||||
if (cipher?.Login == null)
|
||||
{
|
||||
data.PutExtra("canceled", "true");
|
||||
}
|
||||
else
|
||||
{
|
||||
var task = CopyTotpAsync(cipher);
|
||||
data.PutExtra("uri", cipher.Login.Uri);
|
||||
data.PutExtra("username", cipher.Login.Username);
|
||||
data.PutExtra("password", cipher.Login.Password);
|
||||
}
|
||||
if (activity.Parent == null)
|
||||
{
|
||||
activity.SetResult(Result.Ok, data);
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.Parent.SetResult(Result.Ok, data);
|
||||
}
|
||||
activity.Finish();
|
||||
_messagingService.Send("finishMainActivity");
|
||||
if (cipher != null)
|
||||
{
|
||||
var eventTask = _eventServiceFunc().CollectAsync(EventType.Cipher_ClientAutofilled, cipher.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void CloseAutofill()
|
||||
{
|
||||
Autofill(null);
|
||||
}
|
||||
|
||||
public void Background()
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
if (activity.Intent?.GetBooleanExtra("autofillFramework", false) ?? false)
|
||||
{
|
||||
activity.SetResult(Result.Canceled);
|
||||
activity.Finish();
|
||||
}
|
||||
else
|
||||
{
|
||||
activity.MoveTaskToBack(true);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutofillAccessibilityServiceRunning()
|
||||
{
|
||||
var enabledServices = Settings.Secure.GetString(Application.Context.ContentResolver,
|
||||
Settings.Secure.EnabledAccessibilityServices);
|
||||
return Application.Context.PackageName != null &&
|
||||
(enabledServices?.Contains(Application.Context.PackageName) ?? false);
|
||||
}
|
||||
|
||||
public bool AutofillAccessibilityOverlayPermitted()
|
||||
{
|
||||
return Accessibility.AccessibilityHelpers.OverlayPermitted();
|
||||
}
|
||||
|
||||
public bool HasAutofillService()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public void OpenAccessibilityOverlayPermissionSettings()
|
||||
{
|
||||
@@ -768,25 +489,6 @@ namespace Bit.Droid.Services
|
||||
}
|
||||
}
|
||||
|
||||
public bool AutofillServiceEnabled()
|
||||
{
|
||||
if (Build.VERSION.SdkInt < BuildVersionCodes.O)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var afm = (AutofillManager)activity.GetSystemService(
|
||||
Java.Lang.Class.FromType(typeof(AutofillManager)));
|
||||
return afm.IsEnabled && afm.HasEnabledAutofillServices;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenAccessibilitySettings()
|
||||
{
|
||||
try
|
||||
@@ -845,60 +547,11 @@ namespace Bit.Droid.Services
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool DeleteDir(Java.IO.File dir)
|
||||
{
|
||||
if (dir != null && dir.IsDirectory)
|
||||
{
|
||||
var children = dir.List();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
var success = DeleteDir(new Java.IO.File(dir, children[i]));
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return dir.Delete();
|
||||
}
|
||||
else if (dir != null && dir.IsFile)
|
||||
{
|
||||
return dir.Delete();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
public bool SupportsAutofillServices() => Build.VERSION.SdkInt >= BuildVersionCodes.O;
|
||||
|
||||
private bool HasPermission(string permission)
|
||||
{
|
||||
return ContextCompat.CheckSelfPermission(
|
||||
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
||||
}
|
||||
public bool SupportsInlineAutofill() => Build.VERSION.SdkInt >= BuildVersionCodes.R;
|
||||
|
||||
private void AskPermission(string permission)
|
||||
{
|
||||
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
||||
Constants.SelectFilePermissionRequestCode);
|
||||
}
|
||||
|
||||
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
||||
{
|
||||
var intents = new List<IParcelable>();
|
||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
||||
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
||||
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
||||
foreach (var res in listCam)
|
||||
{
|
||||
var packageName = res.ActivityInfo.PackageName;
|
||||
var intent = new Intent(captureIntent);
|
||||
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
|
||||
intent.SetPackage(packageName);
|
||||
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
|
||||
intents.Add(intent);
|
||||
}
|
||||
return intents;
|
||||
}
|
||||
public bool SupportsDrawOver() => Build.VERSION.SdkInt >= BuildVersionCodes.M;
|
||||
|
||||
private Intent RateIntentForUrl(string url, Activity activity)
|
||||
{
|
||||
@@ -917,32 +570,6 @@ namespace Bit.Droid.Services
|
||||
return intent;
|
||||
}
|
||||
|
||||
private async Task CopyTotpAsync(CipherView cipher)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cipher?.Login?.Totp))
|
||||
{
|
||||
var autoCopyDisabled = await _stateService.GetDisableAutoTotpCopyAsync();
|
||||
var canAccessPremium = await _stateService.CanAccessPremiumAsync();
|
||||
if ((canAccessPremium || cipher.OrganizationUseTotp) && !autoCopyDisabled.GetValueOrDefault())
|
||||
{
|
||||
var totpService = ServiceContainer.Resolve<ITotpService>("totpService");
|
||||
var totp = await totpService.GetCodeAsync(cipher.Login.Totp);
|
||||
if (totp != null)
|
||||
{
|
||||
CopyToClipboard(totp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CopyToClipboard(string text)
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var clipboardManager = activity.GetSystemService(
|
||||
Context.ClipboardService) as Android.Content.ClipboardManager;
|
||||
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", text);
|
||||
}
|
||||
|
||||
public float GetSystemFontSizeScale()
|
||||
{
|
||||
var activity = CrossCurrentActivity.Current?.Activity as MainActivity;
|
||||
@@ -953,5 +580,87 @@ namespace Bit.Droid.Services
|
||||
{
|
||||
// for any Android-specific cleanup required after switching accounts
|
||||
}
|
||||
|
||||
public async Task SetScreenCaptureAllowedAsync()
|
||||
{
|
||||
var activity = CrossCurrentActivity.Current?.Activity;
|
||||
if (await _stateService.GetScreenCaptureAllowedAsync())
|
||||
{
|
||||
activity.RunOnUiThread(() => activity.Window.ClearFlags(WindowManagerFlags.Secure));
|
||||
return;
|
||||
}
|
||||
activity.RunOnUiThread(() => activity.Window.AddFlags(WindowManagerFlags.Secure));
|
||||
}
|
||||
|
||||
public void OpenAppSettings()
|
||||
{
|
||||
var intent = new Intent(Android.Provider.Settings.ActionApplicationDetailsSettings);
|
||||
intent.AddFlags(ActivityFlags.NewTask);
|
||||
var uri = Android.Net.Uri.FromParts("package", Application.Context.PackageName, null);
|
||||
intent.SetData(uri);
|
||||
Application.Context.StartActivity(intent);
|
||||
}
|
||||
|
||||
public void CloseExtensionPopUp()
|
||||
{
|
||||
// only used by iOS
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public string GetAutofillAccessibilityDescription()
|
||||
{
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.LollipopMr1)
|
||||
{
|
||||
return AppResources.AccessibilityDescription;
|
||||
}
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||
{
|
||||
return AppResources.AccessibilityDescription2;
|
||||
}
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.NMr1)
|
||||
{
|
||||
return AppResources.AccessibilityDescription3;
|
||||
}
|
||||
|
||||
return AppResources.AccessibilityDescription4;
|
||||
}
|
||||
|
||||
public string GetAutofillDrawOverDescription()
|
||||
{
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
|
||||
{
|
||||
return AppResources.DrawOverDescription;
|
||||
}
|
||||
if (Build.VERSION.SdkInt <= BuildVersionCodes.NMr1)
|
||||
{
|
||||
return AppResources.DrawOverDescription2;
|
||||
}
|
||||
|
||||
return AppResources.DrawOverDescription3;
|
||||
}
|
||||
|
||||
private void SetNumericKeyboardTo(EditText editText)
|
||||
{
|
||||
editText.InputType = InputTypes.ClassNumber | InputTypes.NumberFlagDecimal | InputTypes.NumberFlagSigned;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
editText.KeyListener = DigitsKeyListener.GetInstance(false, false);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
|
||||
class BasicDialogWithResultCancelListener : Java.Lang.Object, IDialogInterfaceOnCancelListener
|
||||
{
|
||||
private readonly TaskCompletionSource<ValidatablePromptResponse?> _taskCompletionSource;
|
||||
|
||||
public BasicDialogWithResultCancelListener(TaskCompletionSource<ValidatablePromptResponse?> taskCompletionSource)
|
||||
{
|
||||
_taskCompletionSource = taskCompletionSource;
|
||||
}
|
||||
|
||||
public void OnCancel(IDialogInterface dialog)
|
||||
{
|
||||
_taskCompletionSource?.TrySetResult(null);
|
||||
dialog?.Dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
279
src/Android/Services/FileService.cs
Normal file
@@ -0,0 +1,279 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Android;
|
||||
using Android.Content;
|
||||
using Android.Content.PM;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Android.Webkit;
|
||||
using AndroidX.Core.App;
|
||||
using AndroidX.Core.Content;
|
||||
using Bit.App.Resources;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Abstractions;
|
||||
using Plugin.CurrentActivity;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class FileService : IFileService
|
||||
{
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IBroadcasterService _broadcasterService;
|
||||
|
||||
private bool _cameraPermissionsDenied;
|
||||
|
||||
public FileService(IStateService stateService, IBroadcasterService broadcasterService)
|
||||
{
|
||||
_stateService = stateService;
|
||||
_broadcasterService = broadcasterService;
|
||||
|
||||
_broadcasterService.Subscribe(nameof(FileService), (message) =>
|
||||
{
|
||||
if (message.Command == "selectFileCameraPermissionDenied")
|
||||
{
|
||||
_cameraPermissionsDenied = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public bool OpenFile(byte[] fileData, string id, string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var intent = BuildOpenFileIntent(fileData, fileName);
|
||||
if (intent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
activity.StartActivity(intent);
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool CanOpenFile(string fileName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var intent = BuildOpenFileIntent(new byte[0], string.Concat("opentest_", fileName));
|
||||
if (intent == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var activities = activity.PackageManager.QueryIntentActivities(intent,
|
||||
PackageInfoFlags.MatchDefaultOnly);
|
||||
return (activities?.Count ?? 0) > 0;
|
||||
}
|
||||
catch { }
|
||||
return false;
|
||||
}
|
||||
|
||||
private Intent BuildOpenFileIntent(byte[] fileData, string fileName)
|
||||
{
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if (extension == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if (mimeType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var cachePath = activity.CacheDir;
|
||||
var filePath = Path.Combine(cachePath.Path, fileName);
|
||||
File.WriteAllBytes(filePath, fileData);
|
||||
var file = new Java.IO.File(cachePath, fileName);
|
||||
if (!file.IsFile)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var intent = new Intent(Intent.ActionView);
|
||||
var uri = FileProvider.GetUriForFile(activity.ApplicationContext,
|
||||
"com.x8bit.bitwarden.fileprovider", file);
|
||||
intent.SetDataAndType(uri, mimeType);
|
||||
intent.SetFlags(ActivityFlags.GrantReadUriPermission);
|
||||
return intent;
|
||||
}
|
||||
catch { }
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool SaveFile(byte[] fileData, string id, string fileName, string contentUri)
|
||||
{
|
||||
try
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
|
||||
if (contentUri != null)
|
||||
{
|
||||
var uri = Android.Net.Uri.Parse(contentUri);
|
||||
var stream = activity.ContentResolver.OpenOutputStream(uri);
|
||||
// Using java bufferedOutputStream due to this issue:
|
||||
// https://github.com/xamarin/xamarin-android/issues/3498
|
||||
var javaStream = new Java.IO.BufferedOutputStream(stream);
|
||||
javaStream.Write(fileData);
|
||||
javaStream.Flush();
|
||||
javaStream.Close();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Prompt for location to save file
|
||||
var extension = MimeTypeMap.GetFileExtensionFromUrl(fileName.Replace(' ', '_').ToLower());
|
||||
if (extension == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
string mimeType = MimeTypeMap.Singleton.GetMimeTypeFromExtension(extension);
|
||||
if (mimeType == null)
|
||||
{
|
||||
// Unable to identify so fall back to generic "any" type
|
||||
mimeType = "*/*";
|
||||
}
|
||||
|
||||
var intent = new Intent(Intent.ActionCreateDocument);
|
||||
intent.SetType(mimeType);
|
||||
intent.AddCategory(Intent.CategoryOpenable);
|
||||
intent.PutExtra(Intent.ExtraTitle, fileName);
|
||||
|
||||
activity.StartActivityForResult(intent, Core.Constants.SaveFileRequestCode);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task ClearCacheAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
DeleteDir(CrossCurrentActivity.Current.Activity.CacheDir);
|
||||
await _stateService.SetLastFileCacheClearAsync(DateTime.UtcNow);
|
||||
}
|
||||
catch (Exception) { }
|
||||
}
|
||||
|
||||
public Task SelectFileAsync()
|
||||
{
|
||||
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
|
||||
var hasStorageWritePermission = !_cameraPermissionsDenied &&
|
||||
HasPermission(Manifest.Permission.WriteExternalStorage);
|
||||
var additionalIntents = new List<IParcelable>();
|
||||
if (activity.PackageManager.HasSystemFeature(PackageManager.FeatureCamera))
|
||||
{
|
||||
var hasCameraPermission = !_cameraPermissionsDenied && HasPermission(Manifest.Permission.Camera);
|
||||
if (!_cameraPermissionsDenied && !hasStorageWritePermission)
|
||||
{
|
||||
AskPermission(Manifest.Permission.WriteExternalStorage);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
if (!_cameraPermissionsDenied && !hasCameraPermission)
|
||||
{
|
||||
AskPermission(Manifest.Permission.Camera);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
if (!_cameraPermissionsDenied && hasCameraPermission && hasStorageWritePermission)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tmpDir = new Java.IO.File(activity.FilesDir, Constants.TEMP_CAMERA_IMAGE_DIR);
|
||||
var file = new Java.IO.File(tmpDir, Constants.TEMP_CAMERA_IMAGE_NAME);
|
||||
if (!file.Exists())
|
||||
{
|
||||
file.ParentFile.Mkdirs();
|
||||
file.CreateNewFile();
|
||||
}
|
||||
var outputFileUri = FileProvider.GetUriForFile(activity,
|
||||
"com.x8bit.bitwarden.fileprovider", file);
|
||||
additionalIntents.AddRange(GetCameraIntents(outputFileUri));
|
||||
}
|
||||
catch (Java.IO.IOException) { }
|
||||
}
|
||||
}
|
||||
|
||||
var docIntent = new Intent(Intent.ActionOpenDocument);
|
||||
docIntent.AddCategory(Intent.CategoryOpenable);
|
||||
docIntent.SetType("*/*");
|
||||
var chooserIntent = Intent.CreateChooser(docIntent, AppResources.FileSource);
|
||||
if (additionalIntents.Count > 0)
|
||||
{
|
||||
chooserIntent.PutExtra(Intent.ExtraInitialIntents, additionalIntents.ToArray());
|
||||
}
|
||||
activity.StartActivityForResult(chooserIntent, Core.Constants.SelectFileRequestCode);
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private bool DeleteDir(Java.IO.File dir)
|
||||
{
|
||||
if (dir is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dir.IsDirectory)
|
||||
{
|
||||
var children = dir.List();
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
var success = DeleteDir(new Java.IO.File(dir, children[i]));
|
||||
if (!success)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return dir.Delete();
|
||||
}
|
||||
|
||||
if (dir.IsFile)
|
||||
{
|
||||
return dir.Delete();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasPermission(string permission)
|
||||
{
|
||||
return ContextCompat.CheckSelfPermission(
|
||||
CrossCurrentActivity.Current.Activity, permission) == Permission.Granted;
|
||||
}
|
||||
|
||||
private void AskPermission(string permission)
|
||||
{
|
||||
ActivityCompat.RequestPermissions(CrossCurrentActivity.Current.Activity, new string[] { permission },
|
||||
Core.Constants.SelectFilePermissionRequestCode);
|
||||
}
|
||||
|
||||
private List<IParcelable> GetCameraIntents(Android.Net.Uri outputUri)
|
||||
{
|
||||
var intents = new List<IParcelable>();
|
||||
var pm = CrossCurrentActivity.Current.Activity.PackageManager;
|
||||
var captureIntent = new Intent(MediaStore.ActionImageCapture);
|
||||
var listCam = pm.QueryIntentActivities(captureIntent, 0);
|
||||
foreach (var res in listCam)
|
||||
{
|
||||
var packageName = res.ActivityInfo.PackageName;
|
||||
var intent = new Intent(captureIntent);
|
||||
intent.SetComponent(new ComponentName(packageName, res.ActivityInfo.Name));
|
||||
intent.SetPackage(packageName);
|
||||
intent.PutExtra(MediaStore.ExtraOutput, outputUri);
|
||||
intents.Add(intent);
|
||||
}
|
||||
return intents;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Bit.App.Abstractions;
|
||||
using Bit.App.Models;
|
||||
@@ -25,13 +26,13 @@ namespace Bit.Droid.Services
|
||||
try
|
||||
{
|
||||
var fallback = ToDotnetFallbackLanguage(new PlatformCulture(netLanguage));
|
||||
Console.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
|
||||
Debug.WriteLine(netLanguage + " failed, trying " + fallback + " (" + e1.Message + ")");
|
||||
ci = new CultureInfo(fallback);
|
||||
}
|
||||
catch (CultureNotFoundException e2)
|
||||
{
|
||||
// iOS language not valid .NET culture, falling back to English
|
||||
Console.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
|
||||
Debug.WriteLine(netLanguage + " couldn't be set, using 'en' (" + e2.Message + ")");
|
||||
ci = new CultureInfo("en");
|
||||
}
|
||||
}
|
||||
@@ -40,7 +41,7 @@ namespace Bit.Droid.Services
|
||||
|
||||
private string AndroidToDotnetLanguage(string androidLanguage)
|
||||
{
|
||||
Console.WriteLine("Android Language:" + androidLanguage);
|
||||
Debug.WriteLine("Android Language:" + androidLanguage);
|
||||
var netLanguage = androidLanguage;
|
||||
if (androidLanguage.StartsWith("zh"))
|
||||
{
|
||||
@@ -79,13 +80,13 @@ namespace Bit.Droid.Services
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
}
|
||||
Console.WriteLine(".NET Language/Locale:" + netLanguage);
|
||||
Debug.WriteLine(".NET Language/Locale:" + netLanguage);
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
private string ToDotnetFallbackLanguage(PlatformCulture platCulture)
|
||||
{
|
||||
Console.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
|
||||
Debug.WriteLine(".NET Fallback Language:" + platCulture.LanguageCode);
|
||||
var netLanguage = platCulture.LanguageCode; // use the first part of the identifier (two chars, usually);
|
||||
switch (platCulture.LanguageCode)
|
||||
{
|
||||
@@ -95,7 +96,7 @@ namespace Bit.Droid.Services
|
||||
// add more application-specific cases here (if required)
|
||||
// ONLY use cultures that have been tested and known to work
|
||||
}
|
||||
Console.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
|
||||
Debug.WriteLine(".NET Fallback Language/Locale:" + netLanguage + " (application-specific)");
|
||||
return netLanguage;
|
||||
}
|
||||
|
||||
|
||||
28
src/Android/Services/WatchDeviceService.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Bit.App.Services;
|
||||
using Bit.Core.Abstractions;
|
||||
|
||||
namespace Bit.Droid.Services
|
||||
{
|
||||
public class WatchDeviceService : BaseWatchDeviceService
|
||||
{
|
||||
public WatchDeviceService(ICipherService cipherService,
|
||||
IEnvironmentService environmentService,
|
||||
IStateService stateService,
|
||||
IVaultTimeoutService vaultTimeoutService)
|
||||
: base(cipherService, environmentService, stateService, vaultTimeoutService)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool IsSupported => false;
|
||||
|
||||
public override bool IsConnected => false;
|
||||
|
||||
protected override bool CanSendData => false;
|
||||
|
||||
protected override Task SendDataToWatchAsync(byte[] rawData) => throw new NotImplementedException();
|
||||
|
||||
protected override void ConnectToWatch() => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ using Java.Lang;
|
||||
namespace Bit.Droid.Tile
|
||||
{
|
||||
[Service(Permission = Manifest.Permission.BindQuickSettingsTile, Label = "@string/AutoFillTile",
|
||||
Icon = "@drawable/shield")]
|
||||
Icon = "@drawable/shield", Exported = true)]
|
||||
[IntentFilter(new string[] { ActionQsTile })]
|
||||
[Register("com.x8bit.bitwarden.AutofillTileService")]
|
||||
public class AutofillTileService : TileService
|
||||
|
||||
@@ -14,7 +14,7 @@ using Java.Lang;
|
||||
|
||||
namespace Bit.Droid.Tile
|
||||
{
|
||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/PasswordGenerator",
|
||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Exported = true, Label = "@string/PasswordGenerator",
|
||||
Icon = "@drawable/generate")]
|
||||
[IntentFilter(new string[] { ActionQsTile })]
|
||||
[Register("com.x8bit.bitwarden.GeneratorTileService")]
|
||||
|
||||
@@ -15,7 +15,8 @@ using Java.Lang;
|
||||
namespace Bit.Droid.Tile
|
||||
{
|
||||
[Service(Permission = Android.Manifest.Permission.BindQuickSettingsTile, Label = "@string/MyVault",
|
||||
Icon = "@drawable/shield")]
|
||||
Icon = "@drawable/shield",
|
||||
Exported = true)]
|
||||
[IntentFilter(new string[] { ActionQsTile })]
|
||||
[Register("com.x8bit.bitwarden.MyVaultTileService")]
|
||||
public class MyVaultTileService : TileService
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Android.App;
|
||||
using Android.Content;
|
||||
using Android.OS;
|
||||
using Android.Provider;
|
||||
using Bit.App.Utilities;
|
||||
|
||||
@@ -47,5 +49,22 @@ namespace Bit.Droid.Utilities
|
||||
await AppHelpers.SetPreconfiguredSettingsAsync(dict);
|
||||
}
|
||||
}
|
||||
|
||||
public static PendingIntentFlags AddPendingIntentMutabilityFlag(PendingIntentFlags pendingIntentFlags, bool isMutable)
|
||||
{
|
||||
//Mutable flag was added on API level 31
|
||||
if (isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.S)
|
||||
{
|
||||
return pendingIntentFlags | PendingIntentFlags.Mutable;
|
||||
}
|
||||
|
||||
//Immutable flag was added on API level 23
|
||||
if (!isMutable && Build.VERSION.SdkInt >= BuildVersionCodes.M)
|
||||
{
|
||||
return pendingIntentFlags | PendingIntentFlags.Immutable;
|
||||
}
|
||||
|
||||
return pendingIntentFlags;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#if !FDROID
|
||||
using Bit.Core.Abstractions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AppCenter;
|
||||
using Microsoft.AppCenter.Crashes;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Bit.Droid.Utilities
|
||||
{
|
||||
public class AppCenterHelper
|
||||
{
|
||||
private const string AppSecret = "d3834185-b4a6-4347-9047-b86c65293d42";
|
||||
|
||||
private readonly IAppIdService _appIdService;
|
||||
private readonly IStateService _stateService;
|
||||
|
||||
private string _userId;
|
||||
private string _appId;
|
||||
|
||||
public AppCenterHelper(
|
||||
IAppIdService appIdService,
|
||||
IStateService stateService)
|
||||
{
|
||||
_appIdService = appIdService;
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
public async Task InitAsync()
|
||||
{
|
||||
_userId = await _stateService.GetActiveUserIdAsync();
|
||||
_appId = await _appIdService.GetAppIdAsync();
|
||||
|
||||
AppCenter.Start(AppSecret, typeof(Crashes));
|
||||
AppCenter.SetUserId(_userId);
|
||||
|
||||
Crashes.GetErrorAttachments = (ErrorReport report) =>
|
||||
{
|
||||
return new ErrorAttachmentLog[]
|
||||
{
|
||||
ErrorAttachmentLog.AttachmentWithText(Description, "crshdesc.txt"),
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
public string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return JsonConvert.SerializeObject(new
|
||||
{
|
||||
AppId = _appId,
|
||||
UserId = _userId
|
||||
}, Formatting.Indented);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||