Compare commits

...

261 Commits

Author SHA1 Message Date
Kyle Spearrin
405c4d1706 New Crowdin updates (#1155)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Serbian (Cyrillic))

* New translations copy.resx (Serbian (Cyrillic))

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations copy.resx (Slovak)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (French)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (English, India)

* New translations copy.resx (French)

* New translations copy.resx (French)
2020-11-19 09:05:37 -05:00
Matt Portune
f7e081ba5d bump version to 2.7.0 (#1153) 2020-11-17 15:23:41 -05:00
Matt Portune
c71deb5051 Enhanced autofill settings (#1150)
* enhanced autofill settings

* cleanup
2020-11-17 09:37:57 -05:00
Vincent Salucci
edab722a76 Updated privacy policy links for theming (#1149) 2020-11-13 14:00:01 -06:00
Kyle Spearrin
2d280bd995 New Crowdin updates (#1148)
* New translations AppResources.resx (Romanian)

* New translations copy.resx (Ukrainian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Simplified)

* New translations copy.resx (Ukrainian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Ukrainian)

* New translations copy.resx (Turkish)

* New translations copy.resx (Turkish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Serbian (Cyrillic))

* New translations copy.resx (Serbian (Cyrillic))

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Indonesian)

* New translations copy.resx (Russian)

* New translations copy.resx (English, United Kingdom)

* New translations copy.resx (English, India)

* New translations copy.resx (Norwegian Bokmal)

* New translations copy.resx (Norwegian Bokmal)

* New translations copy.resx (Sinhala)

* New translations copy.resx (Sinhala)

* New translations copy.resx (Malayalam)

* New translations copy.resx (Malayalam)

* New translations AppResources.resx (Malayalam)

* New translations copy.resx (English, United Kingdom)

* New translations AppResources.resx (English, United Kingdom)

* New translations copy.resx (Persian)

* New translations copy.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations copy.resx (Latvian)

* New translations copy.resx (Latvian)

* New translations copy.resx (Estonian)

* New translations copy.resx (Estonian)

* New translations copy.resx (Croatian)

* New translations copy.resx (Croatian)

* New translations copy.resx (Thai)

* New translations copy.resx (Thai)

* New translations copy.resx (Persian)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Russian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Bulgarian)

* New translations copy.resx (German)

* New translations AppResources.resx (German)

* New translations copy.resx (Danish)

* New translations copy.resx (Danish)

* New translations AppResources.resx (Danish)

* New translations copy.resx (Czech)

* New translations copy.resx (Czech)

* New translations copy.resx (Catalan)

* New translations copy.resx (Catalan)

* New translations AppResources.resx (Catalan)

* New translations copy.resx (Bulgarian)

* New translations AppResources.resx (Greek)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Afrikaans)

* New translations copy.resx (Afrikaans)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (French)

* New translations copy.resx (French)

* New translations AppResources.resx (French)

* New translations copy.resx (Romanian)

* New translations copy.resx (German)

* New translations copy.resx (Greek)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Korean)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Polish)

* New translations copy.resx (Polish)

* New translations AppResources.resx (Polish)

* New translations copy.resx (Dutch)

* New translations copy.resx (Dutch)

* New translations AppResources.resx (Dutch)

* New translations copy.resx (Korean)

* New translations copy.resx (Japanese)

* New translations copy.resx (Greek)

* New translations copy.resx (Japanese)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Hungarian)

* New translations copy.resx (Hungarian)

* New translations AppResources.resx (Hungarian)

* New translations copy.resx (Hebrew)

* New translations copy.resx (Hebrew)

* New translations copy.resx (Finnish)

* New translations copy.resx (Finnish)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (English, India)
2020-11-12 21:45:25 -05:00
Trey Greer
27e3c6553e updated english copy for the app stores (#1101) 2020-11-12 15:14:46 -05:00
eliykat
6258a9cff9 reposition buttons in Log In and Unlock pages (#1073)
* reposition buttons in Log In and Unlock pages

-   Log In page: move Log In button to primary location below password
    entry, move Get Your Master Password Hint to the More Options menu
-   Unlock page (Verify Master Password): swap position of Unlock
    and Log Out buttons

* finish changes to improved login ui

- move Log Out button in lock screen to secondary menu
- show Get Hint button on login screen in the iOS autofill login flow

Co-authored-by: Matt Portune <59324545+mportune-bw@users.noreply.github.com>
2020-11-12 11:23:34 -05:00
eliykat
a72f497581 expand contributing guide (#1147) 2020-11-12 09:39:24 -05:00
Matt Portune
311d3dd635 Android 11 inline autofill (#1145)
* Inline autofill support for Android 11 - initial commit

* null check intent before getting bool extra

* Updated xamarin androidx autofill

* fixed broken overlay fallback

* fixed filename

* auto-compat-check cleanup

* simplification
2020-11-10 17:24:24 -05:00
Kyle Spearrin
e80b3e4542 New Crowdin updates (#1146)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Serbian (Cyrillic))

* New translations copy.resx (Serbian (Cyrillic))

* New translations copy.resx (Serbian (Cyrillic))

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Malayalam)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (English, India)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Romanian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations copy.resx (English, India)

* New translations copy.resx (English, India)
2020-11-10 17:20:44 -05:00
Vincent Salucci
82c96555dc [GDPR] Adjusted TOS/Privacy acceptance (#1138)
* GDPR acceptance flows

* Added specialized margin by platform

* Changed property name
2020-11-05 10:50:18 -06:00
Matt Portune
75b6e69d34 testing removal of setup-nuget (#1137) 2020-11-03 13:46:13 -05:00
Matt Portune
532b5f7c33 change msbuild reference to v1 (#1136) 2020-11-03 13:23:31 -05:00
Matt Portune
a841419c30 Use plain shield icon for accessibility overlay for quick identification (#1135) 2020-11-03 12:30:34 -05:00
Matt Portune
730a56380a Replacement of bitmap icons with vector versions (Android) (#1133) 2020-11-02 11:44:39 -05:00
stevenlele
97aa974443 Update support for several browsers and HTML viewer (#1121)
* Update accessibility support for Via browser

* Add Via browser (CN) & HTML viewer to AutofillHelpers

* Add Via browser (CN) & HTML viewer to autofillservice

* Add browsers to AccessibilityHelpers

* Add browsers to AutofillHelpers

* Add browsers to autofillservice
2020-10-21 09:10:31 -04:00
Matt Portune
b2eee8bde7 remove deprecated info.plist key (#1127) 2020-10-20 16:15:13 -04:00
Matt Portune
3cbe932248 Additional support for system theme setting (#1124)
* first pass with iOS 13+ support

* tweaks for ios pre-13

* Added Android support for dark/light splash & detection with default theme

* update cipher cell text color on system theme change (android)
2020-10-20 15:26:25 -04:00
Vincent Salucci
26d5504a2f Added New policy types (only org & require sso) (#1122) 2020-10-19 11:48:42 -05:00
Matt Portune
b163a0fe77 fix for stuck extension sheets when dismissed by swipe (#1117)
* fix for stuck extension sheets when dismissed by swipe

* simplify dismiss action assignments
2020-10-15 14:34:31 -04:00
Contribucious
523e713d7a [KnownUsernameField] Entries update (Top 20 JP enhancement) (#1051)
This adds support for alternative access for NTT DOCOMO (KnownUsernameField > Top 20 Japan).
2020-10-15 13:10:08 -04:00
Contribucious
c7cf634a94 Update support for Ungoogled Chromium browser (#1119) 2020-10-15 12:34:32 -04:00
Contribucious
c8c14396f1 Firefox (+ some forks): switch resource-id priority order (#1116) 2020-10-15 12:24:40 -04:00
Vincent Salucci
e72ccaf440 Initial commit for new user provision flow (#1091) 2020-10-13 15:01:14 -05:00
Matt Portune
0b7e07ebab clear cipher cache when replacing ios autofill identities (#1112)
* clear cipher cache when replacing ios autofill identities

* changed to be service-centric

* support for multiple cache keys

* async suffix

* added cache keys for android
2020-10-13 15:39:36 -04:00
Matt Portune
37e19d9a60 Support for storing multiple biometric integrity states for iOS (#1110)
* support for storing multiple biometric integrity states for iOS

* remove unused var & save new extension bio state upon password validation
2020-10-07 12:18:36 -04:00
yaakovfeldman
8f533bc576 Add support for Iceraven browser (#1106)
* Add support for Iceraven browser

* Remove legacy Firefox support from Iceraven
2020-10-05 09:15:01 -04:00
Matt Portune
096a9561ed workaround for xamarin.android issue with Android 11 emulator debugging (#1102) 2020-10-01 14:36:52 -04:00
Matt Portune
c6ac9376fc re-select support for android tabbed page buttons (#1100)
* re-select support for android tabbed page buttons

* applied Sane Person Technology (tm) to index check

* remove extra line
2020-10-01 14:16:37 -04:00
TheDuffman85
fd55cf6996 Update AddEditPage.xaml (#1097)
Removed Keyboard="Numeric" property from CardNumberEntry. European debit card numbers are alphanumeric. See also https://en.wikipedia.org/wiki/International_Bank_Account_Number.
2020-09-30 10:45:07 -04:00
Matt Portune
0359705361 fix issue with bio unlock button visibility (#1098) 2020-09-30 10:23:55 -04:00
Chad Scharf
e31a7e5236 Remap pt to pt-BR instead of pt-PT and handle valid mapping of pt to pt-BR initially (#1096)
* Rename pt-BR to pt, mapping for crowdin

* Map localized pt to pt-BR

* Revert "Rename pt-BR to pt, mapping for crowdin"

This reverts commit 42a09e14b7.
2020-09-29 10:58:50 -04:00
Kyle Spearrin
bb477908ef map en-IN 2020-09-28 14:22:09 -04:00
Matt Portune
26175fbe1b fix issue with search bar color on iOS (#1095) 2020-09-28 10:14:31 -04:00
Matt Portune
67bc59f6b6 bump version to 2.6.2 (#1094) 2020-09-26 07:41:02 -04:00
Matt Portune
7b358b1bbb biometric integrity check in iOS extensions (#1093) 2020-09-25 21:14:10 -04:00
Trey Greer
0387d5bdd1 Android + iOS screenshot updates (#971)
* added Android Screenshots

* added new iOS Screenshots

* fixed resolution for some screenshots

* fixed resolution for iPad pro #5

* modified iPhone 6+ images
2020-09-23 09:06:44 -04:00
Matt Portune
2ddf624f7d fix for sso login when bio unlock already enabled (#1086) 2020-09-23 09:02:20 -04:00
Indranil012
74b34661a5 To add autofill support for Idm browser (#1085)
* Update AutofillHelpers.cs

* Update autofillservice.xml

* Update AccessibilityHelpers.cs

* Update AccessibilityHelpers.cs
2020-09-22 09:17:47 -04:00
Matt Portune
dc9765ef58 version bumps to 2.6.1 (#1083) 2020-09-21 19:56:49 -04:00
Oscar Hinton
a50e66faf4 Handle if GetState is null in biometric integrity check (#1082) 2020-09-21 12:34:22 -04:00
Matt Portune
0388738e02 fix issue with biometric validation (#1081)
* fix issue with biometric validation

* null check key & cipher
2020-09-21 11:59:56 -04:00
Kyle Spearrin
d33e38012a update languages (#1080) 2020-09-21 10:20:45 -04:00
vachan-maker
785d0b21c6 Update MobileI18nService.cs (#1075) 2020-09-21 08:23:51 -04:00
Kyle Spearrin
db12cd92b7 check authed before checking if token has premium (#1074) 2020-09-18 15:07:32 -04:00
Kyle Spearrin
52261f99d7 New Crowdin updates (#1071)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Bulgarian)
2020-09-15 12:54:53 -04:00
Kyle Spearrin
692dc154ef New Crowdin updates (#1070)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Dutch)

* New translations copy.resx (Korean)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Malayalam)
2020-09-14 10:49:42 -04:00
Matt Portune
22101d8f4a fix for passphrase generator persistent settings (#1065) 2020-09-10 12:19:02 -04:00
Matt Portune
f68db90b1f fix state comparison (value vs instance) (#1063) 2020-09-09 11:51:28 -04:00
Kyle Spearrin
5e680531da New Crowdin updates (#1062)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Greek)

* New translations copy.resx (Latvian)

* New translations copy.resx (Latvian)

* New translations AppResources.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Sinhala)

* New translations AppResources.resx (Malayalam)

* New translations copy.resx (Malayalam)

* New translations copy.resx (Malayalam)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Latvian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)
2020-09-08 11:30:08 -04:00
Matt Portune
93cd31018e publish to google play internal test track (#1061)
publish directly to internal test track to skip play store approval process for internal testers (artifact can then be manually promoted to alpha > beta > prod like before).  This should bring the testing experience up to speed with TestFlight for iOS (i.e. push code > ci build > immediate availability)
2020-09-06 20:18:44 -04:00
Chad Scharf
277c570723 version bump (#1060) 2020-09-05 21:50:38 -04:00
Matt Portune
f1419a75f6 Added SSO flows and functionality (#1047)
* SSO login flow for pre-existing user and no 2FA

* 2FA progress

* 2FA support

* Added SSO flows and functionality

* Handle webauthenticator cancellation gracefully

* updates & bugfixes

* Added state validation to web auth response handling

* SSO auth, account registration, and environment settings support for iOS extensions

* Added SSO prevalidation to auth process

* prevalidation now hitting identity service base url

* additional error handling

* Requested changes

* fixed case
2020-09-03 12:30:40 -04:00
Contribucious
3af08a4727 [KnownUsernameField] Entries update (Yandex) (#1044)
This adds all missing entries for Yandex (exhaustive list of Yandex domain names obtained by contacting their support).
2020-08-20 09:56:11 -04:00
Contribucious
a535cea85f [KnownUsernameField] Fix for the special case "eBay India" (#1041)
See explanations in the PR.
2020-08-17 15:13:13 -04:00
Kyle Spearrin
29e443ed76 base64 url encode/decode heleprs (#1038) 2020-08-14 10:08:50 -04:00
Oscar Hinton
f95cddd05a Return the correct value for valid biometric (#1037) 2020-08-12 15:55:15 -04:00
Oscar Hinton
ae28de4159 Invalidate biometric on change (#1026)
* Initial working version for Android

* Add a fallback for when upgrading from older app version.

* Ensure biometric validity is re-checked on focus

* Only setup biometric integrity key if biometric is turned on.

* Fix styling according to comments

* Fallback for Android 5.

* Improve comment

* Add boilerplate for iOS

* Change BiometricService to public

* Untested iOS implementation.

* Convert IBiometricService to async. Fix code style for iOS.

* Base64 NSData.

* Review comments for Android BiometricService.

* Rename methods in BiometricService to append Async

* Ensure we wait for async SetupBiometricAsync.

* Update BiometricService.cs

Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-08-08 21:33:49 -04:00
Contribucious
39de2c1d25 [KnownUsernameField] Entries update (main ones) (#1034)
* [KUF] Fix Google; Add natl/dskp Amazon/eBay & Atlassian+Bitly ENT.+Tumblr+Yandex

... also, add missing OAuth support for PayPal. And add "My docomo" from NTT DOCOMO, as part of a Top 20 Japan.

* Less spacing

* Even less spacing

* Additional reduction of spacing

* [Perf. optim.] Google: most frequently used entry in first position
2020-08-07 11:02:08 -04:00
Contribucious
22570e08aa [KnownUsernameField] Engine update (make the system more flexible) (#1011)
* [KnownUsernameField] Engine update (make the system more flexible)

* [KnownUsernameField] Engine update (make the system more flexible)

* Use of tuples array instead of multidimensional array (string[,])

* Use of tuples array instead of multidimensional array (string[,])

* [FIX] IndexOf -> Contains
2020-08-06 12:38:25 -04:00
aaxdev
3b4ef4d238 Feature sync on refresh (#937)
* Added new option: Sync on refresh

* Removed unused field

* Fixed refreshing on disappearing & unnecessary codes removed

* Requested changes

* Calling storage service instead of a dedicated service function (mobile-specific)
2020-08-05 13:19:27 -04:00
Chad Scharf
c5a71c4304 Fix Pin cannot be hidden after showing it #1025 (#1027) 2020-07-30 13:46:06 -04:00
K. Sasa
4f37c2cb73 Replace copy value button fa-clipboard(&#xf0ea;) to fa-clone(&#xf24d;) (#1024) 2020-07-27 13:22:27 -04:00
Chad Scharf
c1ec97055c Added android:installLocation=internalOnly (#1019) 2020-07-23 09:19:11 -04:00
Matt Smith
086c71126f Allow only non-deleted creds to be added to autofill (#1015) 2020-07-16 11:53:02 -05:00
Matt Smith
10a78c1c94 Modified listing of website from Hostname to Host (#1012)
* Modified listing of website from Hostname to Host

* Removed last _hostname artifact.
2020-07-14 13:17:08 -05:00
Gal Szkolnik
cf6021d898 Don't break when CipherType data is null (#1008)
as explained in issue https://github.com/bitwarden/mobile/issues/1006
2020-07-11 09:06:48 -04:00
Matt Smith
ff322cd2dd Modified Permissions Flow (#1005)
Permissions flow for Android was causing the white screen on initial permission grant on occasion. Moved permission grant to pre-TOTP page load.
2020-07-08 14:09:08 -05:00
Kyle Spearrin
1a96d3c38e bump version 2020-07-06 14:36:10 -04:00
Kyle Spearrin
cfe84963fa switch to access token, bump version 2020-07-06 13:54:44 -04:00
Kyle Spearrin
d908a599b1 bump version and build fixes 2020-07-06 13:28:19 -04:00
Kyle Spearrin
9b3ddb8da3 bump version via hub release command 2020-07-06 11:56:00 -04:00
Kyle Spearrin
56d994a69d bump version 2020-07-06 11:27:19 -04:00
Kyle Spearrin
67cd17c604 do actions on master push or release event 2020-07-06 11:26:51 -04:00
Kyle Spearrin
f4fb7eb8b7 New Crowdin updates (#1003)
* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Slovak)
2020-07-06 10:39:38 -04:00
Kyle Spearrin
b9021e4331 bump version 2020-07-06 10:36:23 -04:00
Kyle Spearrin
3583836d3e Upload release assets for ios too 2020-07-06 09:55:16 -04:00
Kyle Spearrin
278815119f update workflow for release tasks 2020-07-06 09:50:26 -04:00
Contribucious
1bb678e455 [KnownUsernameField] Compatibility with more browsers (#1000) 2020-07-01 10:31:12 -04:00
Kyle Spearrin
b9e5fc604b New Crowdin updates (#999)
* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Polish)

* New translations copy.resx (Polish)

* New translations copy.resx (Polish)
2020-06-29 11:30:02 -04:00
Kyle Spearrin
b65e8c48ce New Crowdin updates (#996)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Sinhala)

* New translations copy.resx (Sinhala)

* New translations copy.resx (Sinhala)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Romanian)

* New translations copy.resx (Romanian)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Norwegian Bokmal)
2020-06-26 21:28:06 -04:00
Chad Scharf
e59bc1a08e version bump (#994)
* version bump

* version bump + fix manifest XML formatting
2020-06-25 17:30:14 -04:00
Kyle Spearrin
ff994629de Do not process hostname for regex logins (#987) 2020-06-18 11:58:19 -04:00
Matt Portune
a458b9bc88 Workaround for disabling predictive text in visible password fields (#983)
* Workaround for disabling predictive text in visible password fields

* Fix for non-master branch iOS builds (#984)

* Enable extra workflow steps to allow iOS builds to succeed from non-master branch

* re-enable provisioning profile setup
2020-06-16 11:09:26 -04:00
Matt Portune
2834e25151 Fix for non-master branch iOS builds (#984)
* Enable extra workflow steps to allow iOS builds to succeed from non-master branch

* re-enable provisioning profile setup
2020-06-16 09:55:08 -04:00
Oscar Hinton
c2582fe055 Resolve new ciphers defaulting to ViewPassword = false, causing passwords to be non editable. (#982) 2020-06-15 12:33:36 -04:00
Matt Portune
0980219c8d Add autofill/a11y support for ungoogled.chromium browser (#978) 2020-06-12 15:01:23 -04:00
Oscar Hinton
c5f158f1cf Resolve all passwords being hidden after upgrade (#977) 2020-06-12 09:50:36 -04:00
Kyle Spearrin
62afc023c8 print github ref 2020-06-11 16:26:10 -04:00
Oscar Hinton
52ca84946b Add support for hidden passwords (#920)
* Add support for hidden passwords

* Hide TOTP, hide hidden fields.

* Change _cipher to Cipher.
2020-06-11 16:16:43 -04:00
Kyle Spearrin
e64fb39c32 bump version 2020-06-10 13:49:58 -04:00
Kyle Spearrin
1066598150 fix ipa path 2020-06-10 13:30:08 -04:00
Kyle Spearrin
caed8c2cf0 explore paths 2020-06-10 13:13:03 -04:00
Kyle Spearrin
663be2402d list file system 2020-06-10 12:43:52 -04:00
Kyle Spearrin
6fd24c842f get proper ipa for app store 2020-06-10 12:24:52 -04:00
Kyle Spearrin
302631c4fa set CFBundlePackageType 2020-06-10 11:54:21 -04:00
Kyle Spearrin
8a94623b2b type ios 2020-06-10 11:48:02 -04:00
Kyle Spearrin
016dfdb455 dont archive for app store if not master 2020-06-10 11:33:43 -04:00
Kyle Spearrin
097415385e deploy to app store 2020-06-10 11:32:16 -04:00
Kyle Spearrin
8e6c6e04a3 cleanup export, ipa artifact 2020-06-10 10:53:57 -04:00
Kyle Spearrin
81a30e580e export ipa for app store 2020-06-10 10:02:15 -04:00
Kyle Spearrin
676efe7253 ls archive folder 2020-06-10 09:24:27 -04:00
Kyle Spearrin
39e0e77824 archive on build 2020-06-10 09:03:12 -04:00
Kyle Spearrin
363f5be8ff increment version for ios 2020-06-09 21:23:10 -04:00
Kyle Spearrin
df15fa2f0e move stuff to setup keychain script 2020-06-09 20:47:46 -04:00
Kyle Spearrin
e8c1fbb86f allow codesign to access certs and key 2020-06-09 20:36:44 -04:00
Kyle Spearrin
df986b9ecf timeout settings are for build.keychain 2020-06-09 20:32:23 -04:00
Kyle Spearrin
daabf4bab9 build again 2020-06-09 20:16:03 -04:00
Kyle Spearrin
3095948024 set keychain timeout 2020-06-09 20:14:40 -04:00
Kyle Spearrin
fad289305f add codesign to set-key-partition-list 2020-06-09 17:43:40 -04:00
Kyle Spearrin
97d8f07e0d Merge branch 'master' of github.com:bitwarden/mobile 2020-06-09 17:33:03 -04:00
Kyle Spearrin
dc374c7ce9 try unlocking keychain 2020-06-09 17:32:53 -04:00
Matt Portune
1584475bc3 added hi-res icon for google play (#966)
* added hi-res icon for google play

* moved to proper location
2020-06-09 17:24:19 -04:00
Kyle Spearrin
8f1db25c5c move cloc to its own job 2020-06-09 13:18:22 -04:00
Kyle Spearrin
092b536009 remove debug 2020-06-09 13:14:11 -04:00
Kyle Spearrin
f95bbaa0f7 resolve path of ~ 2020-06-09 13:09:00 -04:00
Kyle Spearrin
bc1f6464d3 change home path to ~ 2020-06-09 13:00:43 -04:00
Kyle Spearrin
b828cd5975 write out homePath 2020-06-09 12:56:53 -04:00
Kyle Spearrin
766b4f7971 create profiles dir path if it doesnt exist 2020-06-09 12:35:39 -04:00
Kyle Spearrin
45e2ffd71e properly escape space 2020-06-09 12:31:18 -04:00
Kyle Spearrin
98757c3f11 use proper params for copy-item 2020-06-09 12:27:40 -04:00
Kyle Spearrin
9aed6d350b setup provisioning profiles 2020-06-09 12:25:09 -04:00
Kyle Spearrin
ca6ce6db32 add mobile profiles 2020-06-09 12:15:06 -04:00
Kyle Spearrin
1c9a6a02af change all seps to / so that it works x-platform 2020-06-09 11:52:23 -04:00
Kyle Spearrin
ba9bafcb5f fix sep character 2020-06-09 11:48:11 -04:00
Kyle Spearrin
0628394122 update path sep character 2020-06-09 11:46:16 -04:00
Kyle Spearrin
99b2cd2ad0 fix pathing 2020-06-09 11:41:03 -04:00
Kyle Spearrin
707a6ecbaa setup ios keychain 2020-06-09 11:37:40 -04:00
Kyle Spearrin
aa2bc40f03 decrypt ios secrets 2020-06-09 11:19:02 -04:00
Kyle Spearrin
5e00e76c4b ios secrets 2020-06-09 11:16:13 -04:00
Kyle Spearrin
0dba992dd4 fix build for app store script path 2020-06-09 10:51:35 -04:00
Kyle Spearrin
b6f61cac9b build play store publisher 2020-06-09 10:47:20 -04:00
Kyle Spearrin
0e41945a8a build ios 2020-06-09 10:47:09 -04:00
Kyle Spearrin
a05e037308 deploy to play store 2020-06-09 10:21:46 -04:00
Kyle Spearrin
f40576c39d print lines of code from CLOC 2020-06-09 10:17:26 -04:00
Kyle Spearrin
9fc810182a print dotnet info 2020-06-09 09:58:28 -04:00
Kyle Spearrin
dd7a52ba08 print version code 2020-06-08 16:10:17 -04:00
Kyle Spearrin
fa6d2a3080 break steps out a bit more 2020-06-08 16:04:04 -04:00
Laurent Tréguier
36efc0c877 Tweak icons to better blend with Android and iOS (#962)
* Add shadow to Android icon

* Add gradient to iOS icon
2020-06-08 15:02:20 -04:00
Kyle Spearrin
e9efcf1b92 rework build scripts 2020-06-08 13:19:44 -04:00
Kyle Spearrin
f4ad1ec8e7 fix secretOutputPath 2020-06-08 13:02:38 -04:00
Kyle Spearrin
0027c21630 more pathing fixes 2020-06-08 13:00:52 -04:00
Kyle Spearrin
6173cab99f fix decryptSecretPath 2020-06-08 12:56:58 -04:00
Kyle Spearrin
80c8097a71 fix paths to ps scripts 2020-06-08 12:55:02 -04:00
Kyle Spearrin
ba3d577125 try shell: pwsh 2020-06-08 12:29:57 -04:00
Kyle Spearrin
8ce4ebb16e powershell Invoke-Expression 2020-06-08 12:25:39 -04:00
Kyle Spearrin
4358ff2338 run powershell commands 2020-06-08 12:21:43 -04:00
Kyle Spearrin
53f9eb083d branches ignore and only on master 2020-06-08 12:17:53 -04:00
Kyle Spearrin
0a3a982cb9 github action android build steps 2020-06-08 12:14:22 -04:00
Kyle Spearrin
c30239b3a8 encrypted GPG files for github actions 2020-06-08 12:14:22 -04:00
Matt Portune
183834689d Fix for accessibility overlay on Android 5 & 6 (#965)
* Fix for accessibility overlay on Android 5 & 6

* changed to skip check outright until API 26
2020-06-08 08:25:24 -04:00
Matt Portune
5da2f3279b biometrics cleanup (#964) 2020-06-08 08:25:13 -04:00
Kyle Spearrin
ec7d87e757 bump version 2020-06-07 11:56:16 -04:00
Kyle Spearrin
4155f69e3c formatting fixes 2020-06-07 11:54:14 -04:00
Matt Portune
473e93ea16 Fix for deadlock in iOS autofill & share extensions (#960) 2020-06-07 00:15:51 -04:00
Kyle Spearrin
fd1941cc3e target iPhone 2020-06-05 15:31:30 -04:00
Kyle Spearrin
dbb51b58db update platforms for ios core builds 2020-06-05 15:30:24 -04:00
Kyle Spearrin
e7d00cfe54 set iphone and sim platforms for ios core 2020-06-05 15:23:15 -04:00
Kyle Spearrin
63453fa962 fix syntax 2020-06-05 14:40:44 -04:00
Kyle Spearrin
689eb7f87b CI updates 2020-06-05 14:39:29 -04:00
Kyle Spearrin
67a4646a50 try out github actions 2020-06-05 13:51:57 -04:00
Kyle Spearrin
a4e0535464 null check all the things (#957) 2020-06-05 13:30:53 -04:00
Kyle Spearrin
61495cd428 bumping ios version to match what was released 2020-06-04 09:48:10 -04:00
Carl
9e7b7415a5 Add support for Bromite's vanilla build of Chromium (#951) 2020-06-02 23:18:30 -04:00
Kyle Spearrin
06a462d48c skip litedb from ios linker 2020-06-02 14:06:53 -04:00
Kyle Spearrin
d3e6e415b9 bump version 2020-06-02 13:08:04 -04:00
Kyle Spearrin
7e633d31a0 sign fdroid builds with a different keystore (#949) 2020-06-02 10:36:16 -04:00
Kyle Spearrin
0fcf006484 Send vaultTimeoutActionChanged event after save (#948) 2020-06-02 09:50:08 -04:00
Kyle Spearrin
48e5b1686f bump version 2020-06-02 09:21:58 -04:00
Kyle Spearrin
608d879c80 New Crowdin translations (#947)
* New translations AppResources.resx (French)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Hindi)
2020-06-02 09:20:55 -04:00
Kyle Spearrin
66055f1d7c Singleton LiteDatabase (#946)
* update litedb. initialize db as a static singleton instance.

* dont need to dispose anymore
2020-06-02 09:13:57 -04:00
Kyle Spearrin
1120bff34d Don't build the keyboard index for autofill if using logout action (#943)
* Don't build the keyboard index for autofill if using logout action

* trigger index rebuild on vault timeout changed event
2020-06-01 14:46:53 -04:00
Kyle Spearrin
24547e67bf check for empty string on malformed URL (#944)
* treat empty string host as null

* use `string.IsNullOrEmpty`
2020-06-01 14:46:37 -04:00
Kyle Spearrin
f8c7285f56 update ios language metadata. 2020-06-01 09:45:15 -04:00
Kyle Spearrin
82eb6a4568 New Crowdin translations (#942)
* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Dutch)
2020-06-01 09:32:26 -04:00
Kyle Spearrin
a3f1f7c78d New Crowdin translations (#941)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (French)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Norwegian Bokmal)
2020-06-01 08:53:20 -04:00
Kyle Spearrin
c377c4a52b bump version 2020-05-30 10:49:51 -04:00
Kyle Spearrin
e5a74cf43c downgrade litedb 2020-05-29 15:26:00 -04:00
Kyle Spearrin
ccf2bf84da allow ios projects to deploy 2020-05-29 15:25:18 -04:00
Kyle Spearrin
fbf3d97d57 check app options ios extension on nfc check (#933) 2020-05-29 15:25:06 -04:00
Chad Scharf
6da0f82ddd Avoid Task.Result usage (#930) 2020-05-29 12:38:26 -04:00
Vincent Salucci
4c3df2e1e1 [Auto Logout] Final review of feature (#932)
* Initial commit of LockService name refactor (#831)

* [Auto-Logout] Update Service layer logic (#835)

* Initial commit of service logic update

* Added default value for action

* Updated ToggleTokensAsync conditional

* Removed unused variables, updated action conditional

* Initial commit: lockOption/lock refactor app layer (#840)

* [Auto-Logout] Settings Refactor - Application Layer Part 2 (#844)

* Initial commit of app layer part 2

* Updated biometrics position

* Reverted resource name refactor

* LockOptions refactor revert

* Updated method casing :: Removed VaultTimeout prefix for timeouts

* Fixed dupe string resource (#854)

* Updated dependency to use VaultTimeoutService (#896)

* [Auto Logout] Xamarin Forms in AutoFill flow (iOS) (#902)

* fix typo in PINRequireMasterPasswordRestart (#900)

* initial commit for xf usage in autofill

* Fixed databinding for hint button

* Updated Two Factor page launch - removed unused imports

* First pass at broadcast/messenger implentation for autofill

* setting theme in extension using theme manager

* extension app resources

* App resources from main app

* fix ref to twoFactorPage

* apply resources to page

* load empty app for sytling in extension

* move ios renderers to ios core

* static ref to resources and GetResourceColor helper

* fix method ref

* move application.current.resources refs to helper

* switch login page alerts to device action dialogs

* run on main thread

* showDialog with device action service

* abstract action sheet to device action service

* add support for yubikey

* add yubikey iimages to extension

* support close button action

* add support to action extension

* remove empty lines

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>

* [Auto Logout] Update lock option to be default value (#929)

* Initial commit - make lock action default

* Removed extra whitespace

Co-authored-by: Jonas Kittner <54631600+theendlessriver13@users.noreply.github.com>
Co-authored-by: Kyle Spearrin <kyle.spearrin@gmail.com>
Co-authored-by: Kyle Spearrin <kspearrin@users.noreply.github.com>
2020-05-29 12:26:36 -04:00
Kyle Spearrin
39e10ff01c New Crowdin translations (#931)
* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Chinese Simplified)
2020-05-29 12:14:35 -04:00
Kyle Spearrin
0b29c6e5a4 Dispost of LiteDatabase instance (#928) 2020-05-28 15:44:27 -04:00
Contribucious
cd3585be58 Early support for future versions of Firefox (+ some forks) (#923)
* Early support for future versions of Firefox

Note: Ideally, a swapping of the two entries of this resource-id value will be done when the time comes; `url_bar_title` becoming Legacy.

* Early support for future versions of some Firefox forks

Note: Ideally, a swapping of the two entries of these resource-id values will be done when the time comes; `url_bar_title` becoming Legacy.
2020-05-26 10:41:27 -04:00
Kyle Spearrin
4e1f91f4d5 New Crowdin translations (#925)
* New translations AppResources.resx (French)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Catalan)

* New translations copy.resx (Belarusian)

* New translations copy.resx (Belarusian)

* New translations AppResources.resx (Norwegian Bokmal)
2020-05-26 10:35:44 -04:00
Kyle Spearrin
272c2e5303 New Crowdin translations (#921)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Vietnamese)

* New translations AppResources.resx (Slovak)

* New translations AppResources.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Portuguese)

* New translations AppResources.resx (Indonesian)

* New translations AppResources.resx (Persian)

* New translations AppResources.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations AppResources.resx (Russian)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Danish)

* New translations AppResources.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations AppResources.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Norwegian Bokmal)
2020-05-23 09:42:00 -04:00
Contribucious
2a82b09f7b Fix support for Firefox Beta (#918) 2020-05-23 09:38:24 -04:00
Contribucious
fd26492577 Partial revert of "Fix support for Firefox Nightly for Developers" (#919)
My mistake: `org.mozilla.fennec_fdroid` is not an "F-Droid version of Firefox Nightly for Developers". This corrects my error.
2020-05-23 09:38:01 -04:00
Contribucious
528b90b694 Fix support for Firefox Nightly for Developers (#917) 2020-05-23 00:20:51 -04:00
Contribucious
9c7961ff6b Adblock Browser: clarification (#916) 2020-05-22 23:00:27 -04:00
Contribucious
954ed6457c Add support for Privacy Browser (Free/Standard). Closes #407 (#915)
* Add Privacy Browser (Free/Standard) to SupportedBrowsers dict

* Add Privacy Browser (Free/Standard) to CompatBrowsers

* Add Privacy Browser (Free/Standard) compatibility-package entries
2020-05-22 21:06:11 -04:00
Matt Portune
5a8fc2dabc Workaround for pasting into editor within scrollview (#913) 2020-05-20 17:23:59 -04:00
Chad Scharf
ce965ba5e1 Soft delete feature (#890)
* [Soft Delete] Added trash folder to mobile (#856)

* [Soft Delete] Added trash folder to mobile

* [Soft Delete] - Revert send to trash label

Co-authored-by: Chad Scharf <cscharf@users.noreply.github.com>

* [Soft Delete] - Fix for iOS autofill index behavior (#859)

* [Soft Delete] Added trash folder to mobile

* [Soft Delete] - Revert send to trash label

* [Soft Delete] - iOS autofill index behavior fix

Co-authored-by: Chad Scharf <cscharf@users.noreply.github.com>

Co-authored-by: Chad Scharf <cscharf@users.noreply.github.com>
2020-05-20 13:35:20 -04:00
Matt Portune
4b9a036e5e Removal of lifecycle hack for 2FA resume flow (#912) 2020-05-20 09:57:48 -04:00
Matt Portune
4576f378cc lock screen drawing & 2FA entry bugfixes (#910)
* lock screen drawing & 2FA entry bugfixes

* cleanup whitespace
2020-05-20 09:09:28 -04:00
Kyle Spearrin
4c65daa995 New Crowdin translations (#908)
* New translations AppResources.resx (Romanian)

* New translations AppResources.resx (Japanese)

* New translations AppResources.resx (Hebrew)

* New translations copy.resx (Hebrew)

* New translations AppResources.resx (Hungarian)

* New translations copy.resx (Hungarian)

* New translations copy.resx (Hungarian)

* New translations AppResources.resx (Italian)

* New translations copy.resx (Italian)

* New translations copy.resx (Italian)

* New translations AppResources.resx (Korean)

* New translations AppResources.resx (Finnish)

* New translations copy.resx (Korean)

* New translations AppResources.resx (Dutch)

* New translations AppResources.resx (Polish)

* New translations copy.resx (Polish)

* New translations copy.resx (Polish)

* New translations AppResources.resx (Portuguese)

* New translations copy.resx (Portuguese)

* New translations copy.resx (Romanian)

* New translations AppResources.resx (Russian)

* New translations copy.resx (Finnish)

* New translations copy.resx (Portuguese)

* New translations AppResources.resx (Greek)

* New translations AppResources.resx (Bulgarian)

* New translations copy.resx (German)

* New translations AppResources.resx (French)

* New translations AppResources.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations copy.resx (Spanish)

* New translations AppResources.resx (Afrikaans)

* New translations copy.resx (Afrikaans)

* New translations copy.resx (Afrikaans)

* New translations AppResources.resx (Belarusian)

* New translations copy.resx (Romanian)

* New translations copy.resx (Bulgarian)

* New translations AppResources.resx (Catalan)

* New translations copy.resx (Catalan)

* New translations copy.resx (Catalan)

* New translations AppResources.resx (Czech)

* New translations copy.resx (Czech)

* New translations AppResources.resx (Danish)

* New translations copy.resx (Danish)

* New translations AppResources.resx (German)

* New translations copy.resx (Bulgarian)

* New translations copy.resx (Persian)

* New translations AppResources.resx (Thai)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Persian)

* New translations AppResources.resx (Persian)

* New translations copy.resx (Indonesian)

* New translations AppResources.resx (Indonesian)

* New translations copy.resx (Thai)

* New translations copy.resx (Estonian)

* New translations copy.resx (Thai)

* New translations AppResources.resx (Croatian)

* New translations copy.resx (Croatian)

* New translations AppResources.resx (Estonian)

* New translations AppResources.resx (Hindi)

* New translations copy.resx (Hindi)

* New translations AppResources.resx (English, United Kingdom)

* New translations copy.resx (English, United Kingdom)

* New translations AppResources.resx (Norwegian Bokmal)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations copy.resx (Portuguese, Brazilian)

* New translations copy.resx (Ukrainian)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Turkish)

* New translations AppResources.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations copy.resx (Slovak)

* New translations AppResources.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations copy.resx (Swedish)

* New translations AppResources.resx (Turkish)

* New translations copy.resx (Turkish)

* New translations AppResources.resx (Ukrainian)

* New translations copy.resx (Vietnamese)

* New translations copy.resx (Ukrainian)

* New translations AppResources.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations copy.resx (Chinese Simplified)

* New translations AppResources.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations copy.resx (Chinese Traditional)

* New translations AppResources.resx (Vietnamese)

* New translations copy.resx (Norwegian Bokmal)
2020-05-18 15:57:30 -04:00
Jonas Kittner
9159e14dd9 fix typo in PINRequireMasterPasswordRestart (#900) 2020-05-13 13:30:07 -04:00
Matt Portune
da661c229c Lib updates (#889)
* lib updates

* updated libs
2020-05-07 17:03:13 -04:00
Matt Portune
973f09f98a Usability improvements to accessibility settings (#888) 2020-05-07 16:32:42 -04:00
Matt Portune
fef370ad88 Fixed issue with autofill icon image (#887) 2020-05-07 13:54:06 -04:00
Matt Portune
bc46b7172d Updated iOS app icon (#886) 2020-05-07 13:20:07 -04:00
Matt Portune
71f546b467 Updated resources (#884) 2020-05-07 09:03:38 -04:00
Contribucious
a66f66c8ac Add support for AVG Browser (#883)
* Add AVG Browser to SupportedBrowsers dict

* Add AVG Browser to CompatBrowsers

* Add AVG Browser compatibility-package entry
2020-05-06 17:32:53 -04:00
Chad Scharf
c2c6ca22db Fixed spacing on known username field list (#882) 2020-05-06 12:11:58 -04:00
Matt Portune
b29440556a Support for establishing a username field without a password field (#880)
* Support for establishing a username field without a password field

* added aws login
2020-05-06 09:48:34 -04:00
Kyle Spearrin
4104f6f772 Color updates for new branding (#879) 2020-05-05 19:13:18 -04:00
Contribucious
01c56dabdf Add support for LG "Dual Window" mode (Android < 7.0 users) (#878) 2020-05-05 17:38:05 -04:00
Contribucious
fafd8f8ee6 [Browser lists] Global clarification (#876)
* AccessibilityHelpers.cs: global clarification

* AutofillHelpers.cs: global clarification

* autofillservice.xml: global clarification
2020-05-05 10:52:54 -04:00
Contribucious
e2033eee23 Add support for Samsung "Multi Window" mode (Android < 7.0 users) (#877)
... including "Pen Window" support, too.
2020-05-05 10:49:55 -04:00
Contribucious
780761664d Add support for Tor Browser (Alpha) (#872)
* Add Tor Browser (Alpha) to SupportedBrowsers dict

* Add Tor Browser (Alpha) to CompatBrowsers

* Add Tor Browser (Alpha) compatibility-package entry
2020-05-04 09:28:28 -04:00
Contribucious
0cfa737eff Add support for GNU IceCat (#873)
* Add GNU IceCat to SupportedBrowsers dict

* Add GNU IceCat to CompatBrowsers

* Add GNU IceCat compatibility-package entry
2020-05-04 09:27:47 -04:00
Contribucious
6463898c5d Add support for Avast Secure Browser (#874)
* Add Avast Secure Browser to SupportedBrowsers dict

* Add Avast Secure Browser to CompatBrowsers

* Add Avast Secure Browser compatibility-package entry
2020-05-04 09:26:44 -04:00
Contribucious
7d4fffa8b6 Adblock Browser: support for v2+ (stable/beta) + Autofill Framework (#868)
* Adblock Browser (AccessibilityHelpers.SupportedBrowsers): add support for v2+ (stable/beta)

Support for older versions (based on Firefox for Android, instead of Chromium) in the list remains.

* Adblock Browser (AutofillHelpers.CompatBrowsers): add it to the list

* Adblock Browser (autofillservice.xml): add it to the list
2020-05-04 09:25:53 -04:00
Contribucious
bcc415ccb3 Add support for Brave Nightly (also: variants "_dev" and "_default") (#869)
* [Brave] Add all other variants to AccessibilityHelpers.SupportedBrowsers

* [Brave] Add all other variants to AutofillHelpers.CompatBrowsers

* [Brave] Add all other variants to autofillservice.xml
2020-05-04 09:23:09 -04:00
Contribucious
827fead347 AccessibilityHelpers.SupportedBrowsers: add Tor Browser, Firefox Lite and Opera Mini Beta (#866) 2020-05-01 14:00:01 -04:00
Matt Portune
36cdc7dd1c Additional URI parsing fix (#865)
* Additional URI parsing fix

* name cleanup
2020-04-30 16:47:29 -04:00
Contribucious
99dceda8ac [Autofill - Browser lists] Sort the entries alphabetically (#864)
* AccessibilityHelpers.SupportedBrowsers: sort the entries alphabetically

* AutofillHelpers.TrustedBrowsers: sort the entries alphabetically

* AutofillHelpers.CompatBrowsers: sort the entries alphabetically

* autofillservice.xml: sort the entries alphabetically
2020-04-30 11:32:56 -04:00
Matt Portune
9d27f111bf Additional uri parsing intelligence (#861) 2020-04-29 13:09:46 -04:00
Matt Portune
69e0906491 Fixes to uri parsing (#860) 2020-04-29 11:04:50 -04:00
Matt Portune
1d48171fd5 Prevent actionsheet command execution if vault is locked (#857) 2020-04-28 10:25:13 -04:00
Matt Portune
cb0a3e3edf Added additional smarts for establishing permission to draw over other apps (#853) 2020-04-24 14:45:11 -04:00
Chris
2b3915a91f Allow a BiometricPrompt to succeed after an initial failure (#791) (#847)
Previously a call to BiometricPrompt.AuthenticationCallback#OnAuthenticationFailed()
was treated as though an unrecoverable failure had occurred. However this is called on
each failed fingerprint match, for example, and is not a terminal failure. Now these
intermittent failures are ignored and a call to #OnAuthenticationError() is recognised
as an unrecoverable failure instead.
2020-04-23 10:28:43 -04:00
Matt Portune
9a403ba0ed Fixed clipboard not clearing on Android 10 (#851) 2020-04-23 10:01:41 -04:00
Matt Portune
0f35885d1c Set Google publisher upload timeout to 3 minutes (#850) 2020-04-22 13:50:25 -04:00
Matt Portune
e3e07b6bfe Only publish to Google Play if master branch (#848) 2020-04-22 10:54:58 -04:00
Contribucious
84a6d1db71 Add support for Vivaldi Sopranos browser. Closes #673 (#845)
* Add Vivaldi Sopranos to SupportedBrowsers dict

* Add Vivaldi Sopranos to CompatBrowsers

* Add Vivaldi Sopranos compatibility-package entry
2020-04-21 08:49:03 -04:00
Contribucious
8cee50299f Add support for Brave Beta browser (#846)
* Add Brave Beta to SupportedBrowsers dict

* Add Brave Beta to CompatBrowsers

* Add Brave Beta compatibility-package entry
2020-04-21 08:48:29 -04:00
Matt Portune
5a78cbef02 Tracing bundles (#842)
* Output to bundle (play store build only) and enable startup tracing

* Update ci build script to recognize bundle (.aab) for Google Play build

* Added bundle awareness to `appveyor.yml` and `Program.cs`

* Log upload exception

* Restore original apk creation alongside the bundle

* Remove link skip as AOT doesn't support linking
2020-04-17 17:06:37 -04:00
Kyle Spearrin
ae66a781d1 dont use encrypted keystore 2020-04-17 12:55:12 -04:00
Kyle Spearrin
5ae3b66e06 add slash 2020-04-16 17:19:56 -04:00
Kyle Spearrin
c3f1cee5d6 remove salt 2020-04-16 17:12:30 -04:00
Kyle Spearrin
5552c42e37 try secure-file ... again.... 2020-04-16 17:11:25 -04:00
Kyle Spearrin
6f146b888b update secure-file tool 2020-04-16 16:54:32 -04:00
Kyle Spearrin
56c09eae90 add salt to upload key decryption step 2020-04-16 16:43:00 -04:00
Kyle Spearrin
6883864e2d sign with upload keystore 2020-04-16 16:31:52 -04:00
Ash Thompson
15bc395454 Add support for Naver Whale. #600 (#837)
* Add Naver Whale to SupportedBrowsers dict

* Add Naver Whale to CompatBrowsers

* Add Naver Whale compatibility-package
2020-04-16 10:08:16 -04:00
Matt Portune
41997d5fe0 Resync autofill compatibility package list with values from AutofillHelpers (#834) 2020-04-15 13:58:00 -04:00
Jose F. Fernandez
ed259cd130 [FIX] Consider default URI match type on filtering (#830) 2020-04-14 14:56:57 -04:00
Vincent Salucci
1dc027cf49 [Autofill] Apply locked autofill flow to logged out state (#827)
* Initial commit: apply locked auto-fill flow to log out auto-fill

* Alphabetized imports

* Removed unnecessary else conditional

* Fix for talkback slider control (#828)

* Initial commit: apply locked auto-fill flow to log out auto-fill

* Alphabetized imports

* Removed unnecessary else conditional

* Fixed variable init order

Co-authored-by: Matt Portune <59324545+mportune-bw@users.noreply.github.com>
2020-04-13 11:32:23 -05:00
Matt Portune
b2abcda111 Fix for talkback slider control (#828) 2020-04-10 13:30:02 -04:00
Matt Portune
d66eaf8855 Additional accessibility tweaks (#825)
* Additional accessibility tweaks

* Cleanup
2020-04-09 14:57:06 -04:00
Dustin Falgout
78cfd82fdd Remove Unnecessary Toast Message (#808)
Remove unnecessary info message when clicking search during the autofill flow. Fixes #807
2020-04-06 13:19:15 -04:00
Matt Portune
8ad44b405d Add support for Vivaldi snapshot browser (#822)
* Add support for Vivaldi snapshot browser

* Added to autofill helpers CompatBrowsers set as well
2020-04-06 11:43:45 -04:00
Matt Portune
4ce4288f68 Updated dependencies (#820) 2020-04-03 17:17:24 -04:00
Matt Portune
d635555576 Tweaks to autofill tile naming & fixed missing plus icon (#819) 2020-04-03 09:57:04 -04:00
Matt Portune
44999557c0 Tweaks to accessibility autofill overlay (#818) 2020-04-02 19:24:31 -04:00
Matt Portune
5d64bab719 Added handled exception tracking for vault export and bumped CsvHelper to latest (#816)
* Added handled exception tracking for vault export and bumped CsvHelper to latest

* Prevent tracking for FDroid builds

* Remove AppCenter import for FDroid builds
2020-04-02 10:30:21 -04:00
Matt Portune
4d3d8b643a Conversion of HockeyApp to AppCenter for crash reporting (#810)
* Conversion of HockeyApp to AppCenter for crash reporting

* Corrected older-style nuget package definition
2020-04-02 09:02:38 -04:00
Kyle Spearrin
915e8cf072 add litedb to linker skip 2020-03-31 13:23:56 -04:00
Chad Scharf
3c18fd7636 Changed all C# control flow block statements to include space between keyword and open paren (#800) 2020-03-28 09:16:28 -04:00
Matt Portune
6c00ac43fc Added Quick Settings tile for triggering accessibility autofill (#795)
* Added Quick Settings tile for triggering accessibility autofill

* Fix crash when tile attempt to cancel non-visible but non-null overlay

* Persist tile state plus cleanup
2020-03-26 12:15:33 -04:00
Kyle Spearrin
5d9a597d8d switch to stable image 2020-03-26 09:27:24 -04:00
Kyle Spearrin
92930955c3 New Crowdin translations (#794)
* New translations AppResources.resx (Korean)

* New translations copy.resx (Korean)

* New translations copy.resx (Korean)

* New translations AppResources.resx (Polish)

* New translations AppResources.resx (Portuguese, Brazilian)

* New translations AppResources.resx (Czech)

* New translations AppResources.resx (Finnish)

* New translations AppResources.resx (German)

* New translations AppResources.resx (Greek)

* New translations copy.resx (Greek)

* New translations copy.resx (Greek)
2020-03-24 14:49:44 -04:00
Matt Portune
145482ea30 Project lib updates and migration (#789)
* Replace 3rd party FAB lib with our own code

* merged

* merged

* WIP

* WIP

* WIP

* WIP

* Updated LiteDB

* Update ZXing libs to 2.4.1

* Missing semicolon

* rename fab style to btn-fab

* Revert project guid modified by VSmac
2020-03-20 17:54:23 -04:00
Matt Portune
6fdb1e3356 Add support for inverse data matrix QR codes (#787) 2020-03-19 17:07:15 -04:00
Vincent Salucci
55dff81b9f Sanitize Password Length (#783)
* Sanitize Password Length

* Formatting updates

* Else if format

Co-authored-by: Vincent Salucci <vsalucci@bitwarden.com>
2020-03-18 13:43:20 -05:00
Matt Portune
ed37972b99 Updated .gitignore with latest from Xamarin repo and removed Android Resource.designer.cs file (#781)
* Updated .gitignore with latest from Xamarin repo and removed Android Resource.designer.cs file

* didn't mean to leave vscode settings line in
2020-03-18 11:42:20 -04:00
Kyle Spearrin
c6b37307b0 use string.Empty 2020-03-16 21:07:54 -04:00
Kyle Spearrin
5d719ba235 bump version 2020-03-16 21:05:56 -04:00
Vincent Salucci
c19795cce0 Fix bug with policy banner visibility (#777)
Co-authored-by: Vincent Salucci <vsalucci@bitwarden.com>
2020-03-16 11:17:45 -05:00
723 changed files with 36043 additions and 30107 deletions

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>ad-hoc</string>
<key>provisioningProfiles</key>
<dict>
<key>com.8bit.bitwarden</key>
<string>Ad hoc: Bitwarden 2020</string>
<key>com.8bit.bitwarden.autofill</key>
<string>Ad hoc: Autofill 2020</string>
<key>com.8bit.bitwarden.find-login-action-extension</key>
<string>Ad hoc: Extension 2020</string>
</dict>
</dict>
</plist>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>com.8bit.bitwarden</key>
<string>Dist: Bitwarden 2020</string>
<key>com.8bit.bitwarden.autofill</key>
<string>Dist: Autofill 2020</string>
<key>com.8bit.bitwarden.find-login-action-extension</key>
<string>Dist: Extension 2020</string>
</dict>
</dict>
</plist>

13
.github/scripts/android/build.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration
)
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/p:Configuration=$configuration"

View File

@@ -0,0 +1,75 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appPath = $($rootPath + "/src/App/App.csproj");
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
Write-Output "########################################"
Write-Output "##### Clean Android and App"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:Clean" "/p:Configuration=FDroid"
msbuild "$($appPath)" "/t:Clean" "/p:Configuration=FDroid"
Write-Output "########################################"
Write-Output "##### Backup project files"
Write-Output "########################################"
Copy-Item $androidManifest $($androidManifest + ".original");
Copy-Item $androidPath $($androidPath + ".original");
Copy-Item $appPath $($appPath + ".original");
Write-Output "########################################"
Write-Output "##### Cleanup Android Manifest"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidManifest);
$nsAndroid=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$nsAndroid.AddNamespace("android", "http://schemas.android.com/apk/res/android");
$firebaseReceiver1=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdInternalReceiver']", `
$nsAndroid);
$firebaseReceiver1.ParentNode.RemoveChild($firebaseReceiver1);
$firebaseReceiver2=$xml.SelectSingleNode(`
"/manifest/application/receiver[@android:name='com.google.firebase.iid.FirebaseInstanceIdReceiver']", `
$nsAndroid);
$firebaseReceiver2.ParentNode.RemoveChild($firebaseReceiver2);
$xml.Save($androidManifest);
Write-Output "########################################"
Write-Output "##### Uninstall from Android.csproj"
Write-Output "########################################"
$xml=New-Object XML;
$xml.Load($androidPath);
$ns=New-Object System.Xml.XmlNamespaceManager($xml.NameTable);
$ns.AddNamespace("ns", $xml.DocumentElement.NamespaceURI);
$firebaseNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.Firebase.Messaging']", $ns);
$firebaseNode.ParentNode.RemoveChild($firebaseNode);
$safetyNetNode=$xml.SelectSingleNode(`
"/ns:Project/ns:ItemGroup/ns:PackageReference[@Include='Xamarin.GooglePlayServices.SafetyNet']", $ns);
$safetyNetNode.ParentNode.RemoveChild($safetyNetNode);
$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);

View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
cd $GITHUB_WORKSPACE
mkdir dist
cp CNAME ./dist
cd store
chmod 600 fdroid/config.py fdroid/keystore.jks
mkdir -p temp/fdroid
TEMP_DIR="$GITHUB_WORKSPACE/store/temp/fdroid"
cd fdroid
echo "keypass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "keystorepass=\"$FDROID_STORE_KEYSTORE_PASSWORD\"" >>config.py
echo "local_copy_dir=\"$TEMP_DIR\"" >>config.py
mkdir -p repo
curl -Lo repo/com.x8bit.bitwarden-fdroid.apk \
https://github.com/bitwarden/mobile/releases/download/$RELEASE_TAG_NAME/com.x8bit.bitwarden-fdroid.apk
fdroid update
fdroid server update
cd ..
rm -rf temp/fdroid/archive
mv -v temp/fdroid ../dist
cd fdroid
cp index.html btn.png qr.png ../../dist/fdroid
cd $GITHUB_WORKSPACE

View File

@@ -0,0 +1,22 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystorePlayPath = $($rootPath + "/src/Android/$appKeystorePlayFilename");
$appKeystoreUploadFilename = "app_upload-keystore.jks";
$appKeystoreUploadPath = $($rootPath + "/src/Android/$appKeystoreUploadFilename");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
$appKeystoreFdroidPath = $($rootPath + "/src/Android/$appKeystoreFdroidFilename");
$googleServicesFilename = "google-services.json";
$googleServicesPath = $($rootPath + "/src/Android/$googleServicesFilename");
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystorePlayFilename + ".gpg") -output $($appKeystorePlayPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreUploadFilename + ".gpg") -output $($appKeystoreUploadPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($appKeystoreFdroidFilename + ".gpg") -output $($appKeystoreFdroidPath)"
Invoke-Expression `
"& `"$decryptSecretPath`" -filename $($googleServicesFilename + ".gpg") -output $($googleServicesPath)"
Invoke-Expression "& `"$decryptSecretPath`" -filename play_creds.json.gpg"

View File

@@ -0,0 +1,9 @@
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$publisherPath = $($rootPath + "/store/google/Publisher/bin/Release/netcoreapp2.0/Publisher.dll");
$credsPath = $($homePath + "/secrets/play_creds.json");
$aabPath = $($rootPath + "/com.x8bit.bitwarden.aab");
$track = "internal";
dotnet $publisherPath $credsPath $aabPath $track

View File

@@ -0,0 +1,16 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 3000 + [int]$env:GITHUB_RUN_NUMBER;
Write-Output "########################################"
Write-Output "##### Setting Version Code $buildNumber"
Write-Output "########################################"
$androidManifest = $($rootPath + "/src/Android/Properties/AndroidManifest.xml");
$xml=New-Object XML;
$xml.Load($androidManifest);
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", [string]$buildNumber);
$xml.Save($androidManifest);

23
.github/scripts/android/sign-fdroid.ps1 vendored Normal file
View File

@@ -0,0 +1,23 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystoreFdroidFilename = "app_fdroid-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign FDroid Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=FDroid" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:FDROID_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreFdroidFilename)" `
"/p:AndroidSigningStorePass=$($env:FDROID_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy FDroid apk to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/FDroid/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden-fdroid.apk");
Copy-Item $signedApkPath $signedApkDestPath

42
.github/scripts/android/sign-play.ps1 vendored Normal file
View File

@@ -0,0 +1,42 @@
$rootPath = $env:GITHUB_WORKSPACE;
$androidPath = $($rootPath + "/src/Android/Android.csproj");
$appKeystorePlayFilename = "app_play-keystore.jks";
$appKeystoreUploadFilename = "app_upload-keystore.jks";
Write-Output "########################################"
Write-Output "##### Sign Google Play Bundle Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=upload" "/p:AndroidSigningKeyPass=$($env:UPLOAD_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystoreUploadFilename)" `
"/p:AndroidSigningStorePass=$($env:UPLOAD_KEYSTORE_PASSWORD)" "/p:AndroidPackageFormat=aab" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Google Play Bundle to project root"
Write-Output "########################################"
$signedAabPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.aab");
$signedAabDestPath = $($rootPath + "/com.x8bit.bitwarden.aab");
Copy-Item $signedAabPath $signedAabDestPath
Write-Output "########################################"
Write-Output "##### Sign APK Release Configuration"
Write-Output "########################################"
msbuild "$($androidPath)" "/t:SignAndroidPackage" "/p:Configuration=Release" "/p:AndroidKeyStore=true" `
"/p:AndroidSigningKeyAlias=bitwarden" "/p:AndroidSigningKeyPass=$($env:PLAY_KEYSTORE_PASSWORD)" `
"/p:AndroidSigningKeyStore=$($appKeystorePlayFilename)" `
"/p:AndroidSigningStorePass=$($env:PLAY_KEYSTORE_PASSWORD)" "/v:quiet"
Write-Output "########################################"
Write-Output "##### Copy Release APK to project root"
Write-Output "########################################"
$signedApkPath = $($rootPath + "/src/Android/bin/Release/com.x8bit.bitwarden-Signed.apk");
$signedApkDestPath = $($rootPath + "/com.x8bit.bitwarden.apk");
Copy-Item $signedApkPath $signedApkDestPath

29
.github/scripts/decrypt-secret.ps1 vendored Normal file
View File

@@ -0,0 +1,29 @@
param (
[Parameter(Mandatory=$true)]
[string] $filename,
[string] $output
)
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$rootPath = $env:GITHUB_WORKSPACE
$secretInputPath = $rootPath + "/.github/secrets"
$input = $secretInputPath + "/" + $filename
$passphrase = $env:DECRYPT_FILE_PASSWORD
$secretOutputPath = $homePath + "/secrets"
if ([string]::IsNullOrEmpty($output)) {
if ($filename.EndsWith(".gpg")) {
$output = $secretOutputPath + "/" + $filename.TrimEnd(".gpg")
} else {
$output = $secretOutputPath + "/" + $filename + ".plaintext"
}
}
if (!(Test-Path -Path $secretOutputPath))
{
New-Item -ItemType Directory -Path $secretOutputPath
}
gpg --quiet --batch --yes --decrypt --passphrase="$passphrase" --output $output $input

29
.github/scripts/ios/build.ps1 vendored Normal file
View File

@@ -0,0 +1,29 @@
param (
[Parameter(Mandatory=$true)]
[string] $configuration,
[string] $platform = "iPhone",
[switch] $archive
)
$rootPath = $env:GITHUB_WORKSPACE;
$iosPath = $($rootPath + "/src/iOS/iOS.csproj");
if ($archive)
{
Write-Output "########################################"
Write-Output "##### Archive $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" `
"/p:ArchiveOnBuild=true" "/t:`"Build`""
Write-Output "########################################"
Write-Output "##### Done"
Write-Output "########################################"
ls ~/Library/Developer/Xcode/Archives
} else
{
Write-Output "########################################"
Write-Output "##### Build $configuration Configuration for $platform Platform"
Write-Output "########################################"
msbuild "$($iosPath)" "/p:Platform=$platform" "/p:Configuration=$configuration" "/t:`"Build`""
}

View File

@@ -0,0 +1,9 @@
$rootPath = $env:GITHUB_WORKSPACE;
$decryptSecretPath = $($rootPath + "/.github/scripts/decrypt-secret.ps1");
Invoke-Expression "& `"$decryptSecretPath`" -filename bitwarden-mobile-key.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename iphone-distribution-cert.p12.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_autofill.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_bitwarden.mobileprovision.gpg"
Invoke-Expression "& `"$decryptSecretPath`" -filename dist_extension.mobileprovision.gpg"

View File

@@ -0,0 +1,5 @@
$rootPath = $env:GITHUB_WORKSPACE;
$ipaPath = "$rootPath/bitwarden-export/Bitwarden.ipa"
xcrun altool --upload-app --type ios --file "$ipaPath" `
--username "$env:APPLE_ID_USERNAME" --password "$env:APPLE_ID_PASSWORD"

13
.github/scripts/ios/export-ipa.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
param (
[Parameter(Mandatory=$true)]
[string] $method
)
$rootPath = $env:GITHUB_WORKSPACE;
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path
$exportOptionsPath = "$rootPath/.github/resources/export-options-$method.plist";
$archivePath = "$homePath/Library/Developer/Xcode/Archives/*/*.xcarchive";
$exportPath = "$rootPath/bitwarden-export";
xcodebuild -exportArchive -archivePath $archivePath -exportPath $exportPath -exportOptionsPlist $exportOptionsPath

View File

@@ -0,0 +1,26 @@
$rootPath = $env:GITHUB_WORKSPACE;
$buildNumber = 100 + [int]$env:GITHUB_RUN_NUMBER;
$bitwardenInfo = $($rootPath + "/src/iOS/Info.plist");
$extensionInfo = $($rootPath + "/src/iOS.Extension/Info.plist");
$autofillInfo = $($rootPath + "/src/iOS.Autofill/Info.plist");
Write-Output "########################################"
Write-Output "##### Setting CFBundleVersion $buildNumber"
Write-Output "########################################"
function Update-Version($file) {
$xml=New-Object XML;
$xml.Load($file);
Select-Xml -xml $xml -XPath "//dict/key[. = 'CFBundleVersion']/following-sibling::string[1]" |
%{
$_.Node.InnerXml = $buildNumber
}
$xml.Save($file);
}
Update-Version $bitwardenInfo
Update-Version $extensionInfo
Update-Version $autofillInfo

13
.github/scripts/ios/setup-keychain.ps1 vendored Normal file
View File

@@ -0,0 +1,13 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$mobileKeyPath = $($secretsPath + "/bitwarden-mobile-key.p12");
$distCertPath = $($secretsPath + "/iphone-distribution-cert.p12");
security create-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p $env:KEYCHAIN_PASSWORD build.keychain
security set-keychain-settings -lut 1200 build.keychain
security import $mobileKeyPath -k build.keychain -P $env:MOBILE_KEY_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security import $distCertPath -k build.keychain -P $env:DIST_CERT_PASSWORD -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $env:KEYCHAIN_PASSWORD build.keychain

21
.github/scripts/ios/setup-profiles.ps1 vendored Normal file
View File

@@ -0,0 +1,21 @@
$homePath = Resolve-Path "~" | Select-Object -ExpandProperty Path;
$secretsPath = $homePath + "/secrets"
$autofillProfilePath = $($secretsPath + "/dist_autofill.mobileprovision");
$bitwardenProfilePath = $($secretsPath + "/dist_bitwarden.mobileprovision");
$extensionProfilePath = $($secretsPath + "/dist_extension.mobileprovision");
$profilesDirPath = "~/Library/MobileDevice/Provisioning Profiles"
if (!(Test-Path -Path $profilesDirPath))
{
New-Item -ItemType Directory -Path $profilesDirPath
}
$autofill_uuid = grep UUID -A1 -a $autofillProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $autofillProfilePath -destination "$profilesDirPath/$autofill_uuid.mobileprovision"
$bitwarden_uuid = grep UUID -A1 -a $bitwardenProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $bitwardenProfilePath -destination "$profilesDirPath/$bitwarden_uuid.mobileprovision"
$extension_uuid = grep UUID -A1 -a $extensionProfilePath | grep -io "[-A-F0-9]\{36\}"
Copy-Item $extensionProfilePath -destination "$profilesDirPath/$extension_uuid.mobileprovision"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,3 @@
Œ
 KØY#æ(˜õÒéEIÖ<49>ß„T?)lÿ´¬—üƒ"=Ä|¯'e•Àm…/~åè' F±É>Üæ÷lŒb¶[ý+RöÌiLŸ"”ñê~V:£³pÜañÚµel%8t„âíŠÔy<¶nÓâÌaU‡wêJD¡ª•ìWe¦9ýÑôž<x8d»O®j\דëûóVq¤¹Ö
Ǻ½-Î#ªîƒó]$l,×Br†«¬d¾·®Â•a-±¥…: Ù:œ„9b,!EmµŽÐQf®DçgˆÑ©x(PìÈ¡~ÊÍ¹êµ –š[§‡!:×;fÜý

Binary file not shown.

BIN
.github/secrets/play_creds.json.gpg vendored Normal file

Binary file not shown.

Binary file not shown.

294
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,294 @@
name: Build
on:
push:
branches-ignore:
- 'l10n_master'
- 'gh-pages'
release:
types:
- published
jobs:
cloc:
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v2
- name: Set up cloc
run: |
sudo apt-get update
sudo apt-get -y install cloc
- name: Print lines of code
run: cloc --vcs git --exclude-dir Resources,store,test,Properties --include-lang C#,XAML
android:
runs-on: windows-latest
steps:
- name: Set up MSBuild
uses: microsoft/setup-msbuild@v1
- name: Print environment
run: |
nuget help
msbuild -version
dotnet --info
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
- name: Decrypt secrets
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Increment version
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/increment-version.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Build Play Store publisher
run: dotnet build ./store/google/Publisher/Publisher.csproj -p:Configuration=Release
- name: Build for Play Store
run: ./.github/scripts/android/build.ps1 -configuration Release
shell: pwsh
- name: Sign for Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-play.ps1
shell: pwsh
env:
PLAY_KEYSTORE_PASSWORD: ${{ secrets.PLAY_KEYSTORE_PASSWORD }}
UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
- name: Upload Play Store .aab artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
with:
name: com.x8bit.bitwarden.aab
path: ./com.x8bit.bitwarden.aab
- name: Upload Play Store .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
with:
name: com.x8bit.bitwarden.apk
path: ./com.x8bit.bitwarden.apk
- name: Clean for F-Droid
run: ./.github/scripts/android/clean-fdroid.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Build for F-Droid
run: ./.github/scripts/android/build.ps1 -configuration FDroid
shell: pwsh
- name: Sign for F-Droid
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/sign-fdroid.ps1
shell: pwsh
env:
FDROID_KEYSTORE_PASSWORD: ${{ secrets.FDROID_KEYSTORE_PASSWORD }}
- name: Upload F-Droid .apk artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
with:
name: com.x8bit.bitwarden-fdroid.apk
path: ./com.x8bit.bitwarden-fdroid.apk
- name: Deploy to Play Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/android/deploy-play.ps1
shell: pwsh
- name: Upload release assets
if: github.event_name == 'release'
run: |
hub release edit `
-a ./com.x8bit.bitwarden.aab `
-a ./com.x8bit.bitwarden.apk `
-a ./com.x8bit.bitwarden-fdroid.apk `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
android-ubuntu:
runs-on: ubuntu-latest
needs: android
steps:
- name: Set up Node
if: github.event_name == 'release'
uses: actions/setup-node@v1
with:
node-version: '10.x'
- name: Set up F-Droid server
if: github.event_name == 'release'
run: |
sudo apt-get -qq update
sudo apt-get -qqy install --no-install-recommends fdroidserver wget
- name: Set up git credentials
if: github.event_name == 'release'
env:
ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
run: |
git config --global credential.helper store
echo "https://${ACCESS_TOKEN}:x-oauth-basic@github.com" >> ~/.git-credentials
git config --global user.email "ci@bitwarden.com"
git config --global user.name "Bitwarden CI"
- name: Print environment
if: github.event_name == 'release'
run: |
node --version
npm --version
git --version
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
if: github.event_name == 'release'
uses: actions/checkout@v2
- name: Install Node dependencies
if: github.event_name == 'release'
run: npm install
- name: Decrypt secrets
if: github.event_name == 'release'
run: |
./.github/scripts/decrypt-secret.ps1 -filename store_fdroid-keystore.jks.gpg `
-output ./store/fdroid/keystore.jks
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Compile for F-Droid Store
if: github.event_name == 'release'
run: |
sudo chmod +x ./.github/scripts/android/compile-fdroid.sh
./.github/scripts/android/compile-fdroid.sh
env:
FDROID_STORE_KEYSTORE_PASSWORD: ${{ secrets.FDROID_STORE_KEYSTORE_PASSWORD }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
- name: Deploy to gh-pages
if: github.event_name == 'release'
run: npm run deploy
ios:
runs-on: macos-latest
steps:
- name: Print environment
run: |
nuget help
msbuild -version
dotnet --info
Write-Output "GitHub ref: $env:GITHUB_REF"
Write-Output "GitHub event: $env:GITHUB_EVENT"
shell: pwsh
env:
GITHUB_REF: ${{ github.ref }}
GITHUB_EVENT: ${{ github.event_name }}
- name: Checkout repo
uses: actions/checkout@v2
- name: Decrypt secrets
run: ./.github/scripts/ios/decrypt-secrets.ps1
shell: pwsh
env:
DECRYPT_FILE_PASSWORD: ${{ secrets.DECRYPT_FILE_PASSWORD }}
- name: Increment version
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/increment-version.ps1
shell: pwsh
- name: Set up keychain
run: ./.github/scripts/ios/setup-keychain.ps1
shell: pwsh
env:
KEYCHAIN_PASSWORD: ${{ secrets.IOS_KEYCHAIN_PASSWORD }}
MOBILE_KEY_PASSWORD: ${{ secrets.IOS_KEY_PASSWORD }}
DIST_CERT_PASSWORD: ${{ secrets.IOS_DIST_CERT_PASSWORD }}
- name: Set up provisioning profiles
run: ./.github/scripts/ios/setup-profiles.ps1
shell: pwsh
- name: Restore packages
run: nuget restore
- name: Archive Build for App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone -archive
shell: pwsh
- name: Build for App Store
if: github.ref != 'refs/heads/master'
run: ./.github/scripts/ios/build.ps1 -configuration AppStore -platform iPhone
shell: pwsh
- name: Export .ipa for App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/export-ipa.ps1 -method app-store
shell: pwsh
- name: Upload App Store .ipa artifact
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
uses: actions/upload-artifact@v2
with:
name: Bitwarden.ipa
path: ./bitwarden-export/Bitwarden.ipa
- name: Deploy to App Store
if: github.ref == 'refs/heads/master' || github.event_name == 'release'
run: ./.github/scripts/ios/deploy-app-store.ps1
shell: pwsh
env:
APPLE_ID_USERNAME: ${{ secrets.APPLE_ID_USERNAME }}
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
- name: Upload release assets
if: github.event_name == 'release'
run: |
hub release edit `
-a ./bitwarden-export/Bitwarden.ipa `
-m "Version $($env:RELEASE_TAG_NAME.TrimStart('v'))" `
$env:RELEASE_TAG_NAME
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}

20
.gitignore vendored
View File

@@ -1,15 +1,28 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# Visual Studio (>=2015) project-specific, machine local files
.vs/
# JetBrains Rider project-specific, machine local files
.idea
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# ignore Xamarin.Android Resource.Designer.cs files
**/*.Droid/**/[Rr]esource.[Dd]esigner.cs
**/*.Android/**/[Rr]esource.[Dd]esigner.cs
**/Android/**/[Rr]esource.[Dd]esigner.cs
**/Droid/**/[Rr]esource.[Dd]esigner.cs
# Xamarin Components
Components/
# Build results
[Dd]ebug/
[Dd]ebugPublic/
@@ -22,9 +35,6 @@ bld/
[Bb]in/
[Oo]bj/
# Visual Studo 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*

View File

@@ -1,4 +1,31 @@
Code contributions are welcome! Please commit any pull requests against the `master` branch.
# 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 [User-to-User Support 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)

View File

@@ -1,5 +1,5 @@
image:
- Visual Studio 2019 Preview
- Visual Studio 2019
- Ubuntu1804
branches:
@@ -55,7 +55,7 @@ before_build:
- ps: |
if($isWindows) {
nuget restore
if($env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
if($env:UPLOAD_KEYSTORE_DEC_SECRET -or$env:KEYSTORE_DEC_SECRET -or $env:GOOGLE_SERVICES_DEC_SECRET -or $env:PLAY_DEC_SECRET) {
nuget install secure-file -ExcludeVersion
}
if($env:GOOGLE_SERVICES_DEC_SECRET) {
@@ -98,6 +98,7 @@ build_script:
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
.\src\Android\ci-build-apks.ps1
if ($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode) }
Push-AppveyorArtifact .\com.x8bit.bitwarden.aab
Push-AppveyorArtifact .\com.x8bit.bitwarden.apk
Push-AppveyorArtifact .\com.x8bit.bitwarden-fdroid.apk
}
@@ -109,12 +110,12 @@ on_success:
npm run deploy
fi
- ps: |
if($isWindows -and $env:PLAY_DEC_SECRET) {
if($isWindows -and $env:PLAY_DEC_SECRET -and $env:APPVEYOR_REPO_BRANCH -eq 'master') {
secure-file\tools\secure-file -decrypt store\google\Publisher\play_creds.json.enc -secret $env:PLAY_DEC_SECRET
cd store\google\Publisher\bin\Release\netcoreapp2.0
dotnet Publisher.dll `
$env:APPVEYOR_BUILD_FOLDER\store\google\Publisher\play_creds.json `
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.apk `
$env:APPVEYOR_BUILD_FOLDER\com.x8bit.bitwarden.aab `
alpha
cd $env:APPVEYOR_BUILD_FOLDER
}

View File

@@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
.editorconfig = .editorconfig
.gitignore = .gitignore
appveyor.yml = appveyor.yml
.github\workflows\build.yml = .github\workflows\build.yml
CONTRIBUTING.md = CONTRIBUTING.md
crowdin.yml = crowdin.yml
README.md = README.md
@@ -238,22 +239,22 @@ Global
{256F9E44-0AF5-4D97-A2F9-DA26080C0A5D}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|Any CPU.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Ad-Hoc|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.Ad-Hoc|iPhoneSimulator.Build.0 = Ad-Hoc|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|Any CPU.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.ActiveCfg = AppStore|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhone.Build.0 = AppStore|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.ActiveCfg = AppStore|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.AppStore|iPhoneSimulator.Build.0 = AppStore|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.ActiveCfg = Debug|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhone.Build.0 = Debug|iPhone
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.ActiveCfg = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|Any CPU.Build.0 = FDroid|Any CPU
{E71F3053-056C-4381-9638-048ED73BDFF6}.FDroid|iPhone.ActiveCfg = FDroid|Any CPU
@@ -279,6 +280,7 @@ Global
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.ActiveCfg = Debug|iPhone
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Build.0 = Debug|iPhone
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhone.Deploy.0 = Debug|iPhone
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{599E0201-420A-4C3E-A7BA-5349F72E0B15}.FDroid|Any CPU.ActiveCfg = FDroid|iPhone
@@ -306,6 +308,7 @@ Global
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.ActiveCfg = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Build.0 = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhone.Deploy.0 = Debug|iPhone
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{324BE76C-38FA-4F11-8BB1-95C7B3B1B545}.FDroid|Any CPU.ActiveCfg = Release|iPhone
@@ -334,6 +337,7 @@ Global
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|Any CPU.ActiveCfg = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.ActiveCfg = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Build.0 = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhone.Deploy.0 = Debug|iPhone
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator
{8A3ECD75-3EC8-4CB3-B3A2-A73A724C279A}.FDroid|Any CPU.ActiveCfg = Release|iPhone

View File

@@ -9,6 +9,7 @@ files:
pt-PT: pt-PT
pt-BR: pt-BR
en-GB: en-GB
en-IN: en-IN
- source: /store/apple/en/copy.resx
translation: /store/apple/%two_letters_code%/copy.resx
update_option: update_as_unapproved
@@ -19,6 +20,7 @@ files:
pt-PT: pt-PT
pt-BR: pt-BR
en-GB: en-GB
en-IN: en-IN
- source: /store/google/en/copy.resx
translation: /store/google/%two_letters_code%/copy.resx
update_option: update_as_unapproved
@@ -29,3 +31,4 @@ files:
pt-BR: pt-BR
pt-PT: pt-PT
en-GB: en-GB
en-IN: en-IN

View File

@@ -4,6 +4,8 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using System;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
namespace Bit.Droid.Accessibility
{
@@ -16,13 +18,13 @@ namespace Bit.Droid.Accessibility
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
LaunchMainActivity(Intent, 932473);
HandleIntent(Intent, 932473);
}
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
LaunchMainActivity(intent, 489729);
HandleIntent(intent, 489729);
}
protected override void OnDestroy()
@@ -33,7 +35,7 @@ namespace Bit.Droid.Accessibility
protected override void OnResume()
{
base.OnResume();
if(!Intent.HasExtra("uri"))
if (!Intent.HasExtra("uri"))
{
Finish();
return;
@@ -44,7 +46,7 @@ namespace Bit.Droid.Accessibility
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if(data == null)
if (data == null)
{
AccessibilityHelpers.LastCredentials = null;
}
@@ -52,7 +54,7 @@ namespace Bit.Droid.Accessibility
{
try
{
if(data.GetStringExtra("canceled") != null)
if (data.GetStringExtra("canceled") != null)
{
AccessibilityHelpers.LastCredentials = null;
}
@@ -78,23 +80,38 @@ namespace Bit.Droid.Accessibility
Finish();
}
private void HandleIntent(Intent callingIntent, int requestCode)
{
if (callingIntent?.GetBooleanExtra("autofillTileClicked", false) ?? false)
{
Intent.RemoveExtra("autofillTileClicked");
var messagingService = ServiceContainer.Resolve<IMessagingService>("messagingService");
messagingService.Send("OnAutofillTileClick");
Finish();
}
else
{
LaunchMainActivity(callingIntent, requestCode);
}
}
private void LaunchMainActivity(Intent callingIntent, int requestCode)
{
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
if(_lastQueriedUri == null)
if (_lastQueriedUri == null)
{
Finish();
return;
}
var now = DateTime.UtcNow;
if(_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
if (_lastLaunch.HasValue && (now - _lastLaunch.Value) <= TimeSpan.FromSeconds(2))
{
return;
}
_lastLaunch = now;
var intent = new Intent(this, typeof(MainActivity));
if(!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
if (!callingIntent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory))
{
intent.PutExtra("uri", _lastQueriedUri);
}

View File

@@ -1,17 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Android.App;
using Android.Content;
using Android.Content.Res;
using Android.Graphics;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
using Bit.App.Resources;
using Bit.Core;
using Plugin.CurrentActivity;
namespace Bit.Droid.Accessibility
{
@@ -20,62 +20,103 @@ namespace Bit.Droid.Accessibility
public static Credentials LastCredentials = null;
public static string SystemUiPackage = "com.android.systemui";
public static string BitwardenTag = "bw_access";
public static bool IsAutofillTileAdded = false;
public static bool IsAccessibilityBroadcastReady = false;
// Be sure to keep these two sections sorted alphabetically
public static Dictionary<string, Browser> SupportedBrowsers => new List<Browser>
{
new Browser("com.android.chrome", "url_bar"),
new Browser("com.chrome.beta", "url_bar"),
new Browser("org.chromium.chrome", "url_bar"),
// [Section A] Entries also present in the list of Autofill Framework
//
// So keep them in sync with:
// - AutofillHelpers.{TrustedBrowsers,CompatBrowsers}
// - Resources/xml/autofillservice.xml
new Browser("com.amazon.cloud9", "url"),
new Browser("com.android.browser", "url"),
new Browser("com.android.chrome", "url_bar"),
new Browser("com.avast.android.secure.browser", "editor"),
new Browser("com.avg.android.secure.browser", "editor"),
new Browser("com.brave.browser", "url_bar"),
new Browser("com.brave.browser_beta", "url_bar"),
new Browser("com.brave.browser_default", "url_bar"),
new Browser("com.brave.browser_dev", "url_bar"),
new Browser("com.brave.browser_nightly", "url_bar"),
new Browser("com.chrome.beta", "url_bar"),
new Browser("com.chrome.canary", "url_bar"),
new Browser("com.chrome.dev", "url_bar"),
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
new Browser("com.ecosia.android", "url_bar"),
new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.microsoft.emmx", "url_bar"),
new Browser("com.mmbox.browser", "search_box"),
new Browser("com.mmbox.xbrowser", "search_box"),
new Browser("com.naver.whale", "url_bar"),
new Browser("com.opera.browser", "url_field"),
new Browser("com.opera.browser.beta", "url_field"),
new Browser("com.opera.mini.native", "url_field"),
new Browser("com.opera.mini.native.beta", "url_field"),
new Browser("com.opera.touch", "addressbarEdit"),
new Browser("com.chrome.dev", "url_bar"),
new Browser("com.chrome.canary", "url_bar"),
new Browser("com.google.android.apps.chrome", "url_bar"),
new Browser("com.google.android.apps.chrome_dev", "url_bar"),
new Browser("org.codeaurora.swe.browser", "url_bar"),
new Browser("org.iron.srware", "url_bar"),
new Browser("com.qwant.liberty", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v4)
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"),
new Browser("com.stoutner.privacybrowser.standard", "url_edittext"),
new Browser("com.vivaldi.browser", "url_bar"),
new Browser("com.vivaldi.browser.snapshot", "url_bar"),
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("org.mozilla.firefox", "url_bar_title"),
new Browser("org.mozilla.firefox_beta", "url_bar_title"),
new Browser("org.mozilla.fennec_aurora", "url_bar_title"),
new Browser("org.mozilla.fennec_fdroid", "url_bar_title"),
new Browser("com.z28j.feel", "g2"), // "g2" for version 0.9.8.4 (984)
new Browser("idm.internet.download.manager", "search"),
new Browser("idm.internet.download.manager.adm.lite", "search"),
new Browser("idm.internet.download.manager.plus", "search"),
new Browser("io.github.forkmaintainers.iceraven", "mozac_browser_toolbar_url_view"),
new Browser("mark.via", "o"), // "o" for version 4.0.7 (20200929)
new Browser("mark.via.gp", "o"), // "o" for version 4.0.7 (20200929)
new Browser("org.adblockplus.browser", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.adblockplus.browser.beta", "url_bar,url_bar_title"), // 2nd = Legacy (before v2)
new Browser("org.bromite.bromite", "url_bar"),
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.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]
new Browser("org.mozilla.fennec_aurora", "mozac_browser_toolbar_url_view,url_bar_title"), // [DEPRECATED]
new Browser("org.mozilla.fennec_fdroid", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.firefox", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.firefox_beta", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy
new Browser("org.mozilla.focus", "display_url"),
new Browser("org.mozilla.klar", "display_url"),
new Browser("org.mozilla.fenix", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.fenix.nightly", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.reference.browser", "mozac_browser_toolbar_url_view"),
new Browser("org.mozilla.rocket", "display_url"),
new Browser("org.torproject.torbrowser", "url_bar_title,mozac_browser_toolbar_url_view"), // 2nd = Anticipation
new Browser("org.torproject.torbrowser_alpha", "mozac_browser_toolbar_url_view,url_bar_title"), // 2nd = Legacy (before v10.0a8)
new Browser("org.ungoogled.chromium", "url_bar"), // [DEPRECATED]
new Browser("org.ungoogled.chromium.extensions.stable", "url_bar"),
new Browser("org.ungoogled.chromium.stable", "url_bar"),
// [Section B] Entries only present here
//
// FIXME: Test the compatibility of these with Autofill Framework
new Browser("acr.browser.barebones", "search"),
new Browser("acr.browser.lightning", "search"),
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
new Browser("com.ghostery.android.ghostery", "search_field"),
new Browser("org.adblockplus.browser", "url_bar_title"),
new Browser("com.htc.sense.browser", "title"),
new Browser("com.amazon.cloud9", "url"),
new Browser("mobi.mgeek.TunnyBrowser", "title"),
new Browser("com.nubelacorp.javelin", "enterUrl"),
new Browser("com.jerky.browser2", "enterUrl"),
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
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"),
new Browser("com.linkbubble.playstore", "url_text"),
new Browser("com.ksmobile.cb", "address_bar_edit_text"),
new Browser("acr.browser.lightning", "search"),
new Browser("acr.browser.barebones", "search"),
new Browser("com.microsoft.emmx", "url_bar"),
new Browser("com.duckduckgo.mobile.android", "omnibarTextInput"),
new Browser("mark.via.gp", "aw"),
new Browser("org.bromite.bromite", "url_bar"),
new Browser("com.kiwibrowser.browser", "url_bar"),
new Browser("com.ecosia.android", "url_bar"),
new Browser("com.qwant.liberty", "url_bar_title"),
new Browser("com.nubelacorp.javelin", "enterUrl"),
new Browser("jp.co.fenrir.android.sleipnir", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_black", "url_text"),
new Browser("jp.co.fenrir.android.sleipnir_test", "url_text"),
new Browser("com.vivaldi.browser", "url_bar"),
new Browser("com.feedback.browser.wjbrowser", "addressbar_url"),
new Browser("mobi.mgeek.TunnyBrowser", "title"),
new Browser("org.iron.srware", "url_bar"),
}.ToDictionary(n => n.PackageName);
// Known packages to skip
@@ -99,11 +140,203 @@ namespace Bit.Droid.Accessibility
"com.treydev.pns"
};
// Be sure to keep these sections sorted alphabetically
public static Dictionary<string, KnownUsernameField> KnownUsernameFields => new List<KnownUsernameField>
{
/**************************************************************************************
* SECTION A ——— World-renowned web sites/applications
*************************************************************************************/
// REM.: For this type of web sites/applications, the Top 100 (SimilarWeb, 2019)
// and the Top 50 (Alexa Internet, 2020) are covered. National variants
// have been added when available. Mobile and desktop versions supported.
//
// A few other popular web sites/applications have also been added.
//
// Could not be added, however:
// web sites/applications that don't use an "id" attribute for their login field.
// NOTE: The case of OAuth compatible web sites/applications that also provide
// a "user ID only" login page in this situation
// was taken into account in the tests as well.
/*
* A
*/
// Amazon ——— ap_email_login = mobile / ap_email = desktop (amazon.co.jp currently uses ap_email in both cases, as of July 2020).
new KnownUsernameField("amazon.ae", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.ca", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.cn", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.co.jp", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.co.uk", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.com", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.com.au", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.com.br", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.com.mx", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.com.tr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.de", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.es", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.fr", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.in", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.it", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.nl", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sa", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
new KnownUsernameField("amazon.sg", new (string, string)[] { ("contains:/ap/signin", "ap_email_login,ap_email") }),
// Amazon Web Services
new KnownUsernameField("signin.aws.amazon.com", new (string, string)[] { ("signin", "resolving_input") }),
// Atlassian
new KnownUsernameField("id.atlassian.com", new (string, string)[] { ("login", "username") }),
/*
* B
*/
// Bitly ——— enterprise users.
new KnownUsernameField("bitly.com", new (string, string)[] { ("/sso/url_slug", "url_slug") }),
/*
* E
*/
// eBay ——— 1st = traditional access / 2nd = direct access (i.e. https://signin.ebay.tld/).
new KnownUsernameField("signin.befr.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.benl.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.cafr.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.at", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.be", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.ca", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.ch", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.co.uk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.com", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.com.au", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.com.hk", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.com.my", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.com.sg", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.de", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.es", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.fr", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.ie", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.in", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.it", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.nl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.ph", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
new KnownUsernameField("signin.ebay.pl", new (string, string)[] { ("iendswith:eBayISAPI.dll", "userid"), ("icontains:/signin/", "userid") }),
/*
* G
*/
// Google ——— 1st = used in most cases (v2) / 2nd = used in some cases (v1).
new KnownUsernameField("accounts.google.com", new (string, string)[] { ("identifier", "identifierId"), ("ServiceLogin", "Email") }),
/*
* P
*/
// PayPal ——— 1st = traditional access / 2nd = access using OAuth.
new KnownUsernameField("paypal.com", new (string, string)[] { ("signin", "email"), ("contains:/connect/", "email") }),
/*
* T
*/
// Tumblr ——— despite "signup" in its ID, it's the login field (the website offers registration if the account doesn't exist).
new KnownUsernameField("tumblr.com", new (string, string)[] { ("login", "signup_determine_email") }),
/*
* Y
*/
// Yandex
new KnownUsernameField("passport.yandex.az", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.by", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.co.il", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.com", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.com.am", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.com.ge", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.com.tr", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.ee", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.fi", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.fr", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.kg", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.kz", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.lt", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.lv", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.md", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.pl", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.ru", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.tj", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.tm", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.ua", new (string, string)[] { ("auth", "passp-field-login") }),
new KnownUsernameField("passport.yandex.uz", new (string, string)[] { ("auth", "passp-field-login") }),
/**************************************************************************************
* SECTION B ——— Top 100 worldwide
*************************************************************************************/
// As of July 2020, all entries that needed to be added from
// Top 100 (SimilarWeb, 2019) and Top 50 (Alexa Internet, 2020)
// matched section A.
//
// Therefore, no entry currently.
/**************************************************************************************
* SECTION C ——— Top 20 for selected countries
*************************************************************************************/
// REM.: For these selected countries, the Top 20 (SimilarWeb, 2020)
// and the Top 20 (Alexa Internet, 2020) are covered.
// Mobile and desktop versions supported.
//
// Could not be added, however:
// web sites/applications that don't use an "id" attribute for their login field.
/*
* Japan
*/
// NTT DOCOMO ——— mainly used for "My docomo".
new KnownUsernameField("cfg.smt.docomo.ne.jp", new (string, string)[] { ("contains:/auth/", "Di_Uid") }),
new KnownUsernameField("id.smt.docomo.ne.jp", new (string, string)[] { ("contains:/cgi7/", "Di_Uid") }),
/**************************************************************************************
* SECTION D ——— Miscellaneous
*************************************************************************************/
/*
* Various entries ——— Following user requests, etc.
*/
// No entry, currently.
/**************************************************************************************
* SECTION Z ——— Special forms
*
* Despite "user ID + password" fields both visible, detection rules required.
*************************************************************************************/
/*
* Main
*/
// No entry, currently.
/*
* Test/example purposes only
*/
// GitHub ——— VERY special case (signup form, just to test the proper functioning of special forms).
new KnownUsernameField("github.com", new (string, string)[] { ("", "user[login]-footer") }),
}.ToDictionary(n => n.UriAuthority);
public static void PrintTestData(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var testNodes = GetWindowNodes(root, e, n => n.ViewIdResourceName != null && n.Text != null, false);
var testNodesData = testNodes.Select(n => new { id = n.ViewIdResourceName, text = n.Text });
foreach(var node in testNodesData)
foreach (var node in testNodesData)
{
System.Diagnostics.Debug.WriteLine("Node: {0} = {1}", node.id, node.text);
}
@@ -112,21 +345,21 @@ namespace Bit.Droid.Accessibility
public static string GetUri(AccessibilityNodeInfo root)
{
var uri = string.Concat(Constants.AndroidAppProtocol, root.PackageName);
if(SupportedBrowsers.ContainsKey(root.PackageName))
if (SupportedBrowsers.ContainsKey(root.PackageName))
{
var browser = SupportedBrowsers[root.PackageName];
AccessibilityNodeInfo addressNode = null;
foreach(var uriViewId in browser.UriViewId.Split(","))
foreach (var uriViewId in browser.UriViewId.Split(","))
{
addressNode = root.FindAccessibilityNodeInfosByViewId(
$"{root.PackageName}:id/{uriViewId}").FirstOrDefault();
if(addressNode != null)
if (addressNode != null)
{
break;
}
}
if(addressNode != null)
if (addressNode != null)
{
uri = ExtractUri(uri, addressNode, browser);
addressNode.Recycle();
@@ -141,35 +374,31 @@ namespace Bit.Droid.Accessibility
return uri;
}
public static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
private static string ExtractUri(string uri, AccessibilityNodeInfo addressNode, Browser browser)
{
if(addressNode?.Text == null)
if (addressNode?.Text == null)
{
return uri;
}
if(addressNode.Text == null)
if (addressNode.Text == null)
{
return uri;
}
uri = browser.GetUriFunction(addressNode.Text)?.Trim();
if(uri != null && uri.Contains("."))
if (uri != null && uri.Contains("."))
{
if(!uri.Contains("://") && !uri.Contains(" "))
var hasHttpProtocol = uri.StartsWith("http://") || uri.StartsWith("https://");
if (!hasHttpProtocol && uri.Contains("."))
{
uri = string.Concat("http://", uri);
}
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
{
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
if(parts.Length > 1)
if (Uri.TryCreate("http://" + uri, UriKind.Absolute, out var uri2))
{
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
if(urlPart != null)
{
uri = urlPart.Trim();
}
return string.Concat("http://", uri);
}
}
if (Uri.TryCreate(uri, UriKind.Absolute, out var uri3))
{
return uri;
}
}
return uri;
}
@@ -179,11 +408,11 @@ namespace Bit.Droid.Accessibility
/// </summary>
public static bool NeedToAutofill(Credentials credentials, string currentUriString)
{
if(credentials == null)
if (credentials == null)
{
return false;
}
if(Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
if (Uri.TryCreate(credentials.LastUri, UriKind.Absolute, out Uri lastUri) &&
Uri.TryCreate(currentUriString, UriKind.Absolute, out Uri currentUri))
{
return lastUri.Host == currentUri.Host;
@@ -200,7 +429,7 @@ namespace Bit.Droid.Accessibility
IEnumerable<AccessibilityNodeInfo> passwordNodes)
{
FillEditText(usernameNode, LastCredentials?.Username);
foreach(var n in passwordNodes)
foreach (var n in passwordNodes)
{
FillEditText(n, LastCredentials?.Password);
}
@@ -208,7 +437,7 @@ namespace Bit.Droid.Accessibility
public static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
{
if(editTextNode == null || value == null)
if (editTextNode == null || value == null)
{
return;
}
@@ -221,35 +450,35 @@ namespace Bit.Droid.Accessibility
Func<AccessibilityNodeInfo, bool> condition, bool disposeIfUnused, NodeList nodes = null,
int recursionDepth = 0)
{
if(nodes == null)
if (nodes == null)
{
nodes = new NodeList();
}
var dispose = disposeIfUnused;
if(n != null && recursionDepth < 100)
if (n != null && recursionDepth < 100)
{
var add = n.WindowId == e.WindowId &&
!(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) &&
condition(n);
if(add)
if (add)
{
dispose = false;
nodes.Add(n);
}
for(var i = 0; i < n.ChildCount; i++)
for (var i = 0; i < n.ChildCount; i++)
{
var childNode = n.GetChild(i);
if(childNode == null)
if (childNode == null)
{
continue;
}
else if(i > 100)
else if (i > 100)
{
Android.Util.Log.Info(BitwardenTag, "Too many child iterations.");
break;
}
else if(childNode.GetHashCode() == n.GetHashCode())
else if (childNode.GetHashCode() == n.GetHashCode())
{
Android.Util.Log.Info(BitwardenTag, "Child node is the same as parent for some reason.");
}
@@ -259,7 +488,7 @@ namespace Bit.Droid.Accessibility
}
}
}
if(dispose)
if (dispose)
{
n?.Recycle();
n?.Dispose();
@@ -267,23 +496,107 @@ namespace Bit.Droid.Accessibility
return nodes;
}
public static void GetNodesAndFill(AccessibilityNodeInfo root, AccessibilityEvent e,
IEnumerable<AccessibilityNodeInfo> passwordNodes)
public static AccessibilityNodeInfo GetUsernameEditText(string uriString,
IEnumerable<AccessibilityNodeInfo> allEditTexts)
{
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts);
FillCredentials(usernameEditText, passwordNodes);
allEditTexts.Dispose();
usernameEditText = null;
}
string uriAuthority = null;
string uriKey = null;
string uriLocalPath = null;
if (Uri.TryCreate(uriString, UriKind.Absolute, out var uri))
{
uriAuthority = uri.Authority;
uriKey = uriAuthority.StartsWith("www.", StringComparison.Ordinal) ? uriAuthority.Substring(4) : uriAuthority;
uriLocalPath = uri.LocalPath;
}
public static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
if (!string.IsNullOrEmpty(uriKey))
{
// Uncomment this to log values necessary for username field discovery
// foreach (var editText in allEditTexts)
// {
// System.Diagnostics.Debug.WriteLine(">>> uriKey: {0}, uriLocalPath: {1}, viewId: {2}", uriKey,
// uriLocalPath, editText.ViewIdResourceName);
// }
if (KnownUsernameFields.ContainsKey(uriKey))
{
var usernameField = KnownUsernameFields[uriKey];
(string UriPathWanted, string UsernameViewId)[] accessOptions = usernameField.AccessOptions;
for (int i = 0; i < accessOptions.Length; i++)
{
string curUriPathWanted = accessOptions[i].UriPathWanted;
string curUsernameViewId = accessOptions[i].UsernameViewId;
bool uriLocalPathMatches = false;
// Case-sensitive comparison
if (curUriPathWanted.StartsWith("startswith:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(11);
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.Ordinal);
}
else if (curUriPathWanted.StartsWith("contains:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(9);
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.Ordinal);
}
else if (curUriPathWanted.StartsWith("endswith:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(9);
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
}
// Case-insensitive comparison
else if (curUriPathWanted.StartsWith("istartswith:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(12);
uriLocalPathMatches = uriLocalPath.StartsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
}
else if (curUriPathWanted.StartsWith("icontains:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(10);
uriLocalPathMatches = uriLocalPath.Contains(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
}
else if (curUriPathWanted.StartsWith("iendswith:", StringComparison.Ordinal))
{
curUriPathWanted = curUriPathWanted.Substring(10);
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.OrdinalIgnoreCase);
}
// Default type of comparison
else
{
uriLocalPathMatches = uriLocalPath.EndsWith(curUriPathWanted, StringComparison.Ordinal);
}
if (uriLocalPathMatches)
{
foreach (var editText in allEditTexts)
{
foreach (var usernameViewId in curUsernameViewId.Split(","))
{
if (usernameViewId == editText.ViewIdResourceName)
{
return editText;
}
}
}
}
}
}
}
// no match found, attempt to establish username field based on password field
return GetUsernameEditTextIfPasswordExists(allEditTexts);
}
private static AccessibilityNodeInfo GetUsernameEditTextIfPasswordExists(
IEnumerable<AccessibilityNodeInfo> allEditTexts)
{
AccessibilityNodeInfo previousEditText = null;
foreach(var editText in allEditTexts)
foreach (var editText in allEditTexts)
{
if(editText.Password)
if (editText.Password)
{
return previousEditText;
}
@@ -295,10 +608,11 @@ namespace Bit.Droid.Accessibility
public static bool IsUsernameEditText(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var allEditTexts = GetWindowNodes(root, e, n => EditText(n), false);
var usernameEditText = GetUsernameEditTextIfPasswordExists(allEditTexts);
var uriString = GetUri(root);
var usernameEditText = GetUsernameEditText(uriString, allEditTexts);
var isUsernameEditText = false;
if(usernameEditText != null)
if (usernameEditText != null)
{
isUsernameEditText = IsSameNode(usernameEditText, e.Source);
}
@@ -309,7 +623,7 @@ namespace Bit.Droid.Accessibility
public static bool IsSameNode(AccessibilityNodeInfo node1, AccessibilityNodeInfo node2)
{
if(node1 != null && node2 != null)
if (node1 != null && node2 != null)
{
return node1.Equals(node2) || node1.GetHashCode() == node2.GetHashCode();
}
@@ -318,15 +632,42 @@ namespace Bit.Droid.Accessibility
public static bool OverlayPermitted()
{
if(Build.VERSION.SdkInt >= BuildVersionCodes.M)
if (Build.VERSION.SdkInt >= BuildVersionCodes.M)
{
return Settings.CanDrawOverlays(Android.App.Application.Context);
}
else
{
// TODO do older android versions require a check?
return true;
if (Settings.CanDrawOverlays(Application.Context))
{
return true;
}
var appOpsMgr = (AppOpsManager)Application.Context.GetSystemService(Context.AppOpsService);
var mode = appOpsMgr.CheckOpNoThrow("android:system_alert_window", Process.MyUid(),
Application.Context.PackageName);
if (mode == AppOpsManagerMode.Allowed || mode == AppOpsManagerMode.Ignored)
{
return true;
}
try
{
var wm = Application.Context.GetSystemService(Context.WindowService)
.JavaCast<IWindowManager>();
if (wm == null)
{
return false;
}
var testView = new View(Application.Context);
var layoutParams = GetOverlayLayoutParams();
wm.AddView(testView, layoutParams);
wm.RemoveView(testView);
return true;
}
catch { }
return false;
}
// older android versions are always true
return true;
}
public static LinearLayout GetOverlayView(Context context)
@@ -338,14 +679,14 @@ namespace Bit.Droid.Accessibility
var icon = (ImageView)view.FindViewById(Resource.Id.icon);
text1.Text = AppResources.AutofillWithBitwarden;
text2.Text = AppResources.GoToMyVault;
icon.SetImageResource(Resource.Drawable.icon);
icon.SetImageResource(Resource.Drawable.shield);
return view;
}
public static WindowManagerLayoutParams GetOverlayLayoutParams()
{
WindowManagerTypes windowManagerType;
if(Build.VERSION.SdkInt >= BuildVersionCodes.O)
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
windowManagerType = WindowManagerTypes.ApplicationOverlay;
}
@@ -365,8 +706,8 @@ namespace Bit.Droid.Accessibility
return layoutParams;
}
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorView, int overlayViewHeight,
bool isOverlayAboveAnchor)
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorView,
int overlayViewHeight, bool isOverlayAboveAnchor)
{
var anchorViewRect = new Rect();
anchorView.GetBoundsInScreen(anchorViewRect);
@@ -374,58 +715,57 @@ namespace Bit.Droid.Accessibility
var anchorViewY = isOverlayAboveAnchor ? anchorViewRect.Top : anchorViewRect.Bottom;
anchorViewRect.Dispose();
if(isOverlayAboveAnchor)
if (isOverlayAboveAnchor)
{
anchorViewY -= overlayViewHeight;
}
anchorViewY -= GetStatusBarHeight();
anchorViewY -= GetStatusBarHeight(service);
return new Point(anchorViewX, anchorViewY);
}
public static Point GetOverlayAnchorPosition(AccessibilityNodeInfo anchorNode, AccessibilityNodeInfo root,
IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight, bool isOverlayAboveAnchor)
public static Point GetOverlayAnchorPosition(AccessibilityService service, AccessibilityNodeInfo anchorNode,
AccessibilityNodeInfo root, IEnumerable<AccessibilityWindowInfo> windows, int overlayViewHeight,
bool isOverlayAboveAnchor)
{
Point point = null;
if(anchorNode != null)
if (anchorNode != null)
{
// Update node's info since this is still a reference from an older event
anchorNode.Refresh();
if(!anchorNode.VisibleToUser)
if (!anchorNode.VisibleToUser)
{
return new Point(-1, -1);
}
if(!anchorNode.Focused)
if (!anchorNode.Focused)
{
return null;
}
// node.VisibleToUser doesn't always give us exactly what we want, so attempt to tighten up the range
// of visibility
var minY = 0;
int maxY;
if(windows != null)
var inputMethodHeight = 0;
if (windows != null)
{
if(IsStatusBarExpanded(windows))
if (IsStatusBarExpanded(windows))
{
return new Point(-1, -1);
}
maxY = GetApplicationVisibleHeight(windows);
inputMethodHeight = GetInputMethodHeight(windows);
}
else
var minY = 0;
var rootNodeHeight = GetNodeHeight(root);
if (rootNodeHeight == -1)
{
var rootNodeHeight = GetNodeHeight(root);
if(rootNodeHeight == -1)
{
return null;
}
maxY = rootNodeHeight - GetNavigationBarHeight();
return null;
}
var maxY = rootNodeHeight - GetNavigationBarHeight(service) - GetStatusBarHeight(service) -
inputMethodHeight;
point = GetOverlayAnchorPosition(anchorNode, overlayViewHeight, isOverlayAboveAnchor);
if(point.Y < minY)
point = GetOverlayAnchorPosition(service, anchorNode, overlayViewHeight, isOverlayAboveAnchor);
if (point.Y < minY)
{
if(isOverlayAboveAnchor)
if (isOverlayAboveAnchor)
{
// view nearing bounds, anchor to bottom
point.X = -1;
@@ -438,9 +778,9 @@ namespace Bit.Droid.Accessibility
point.Y = -1;
}
}
else if(point.Y > maxY)
else if (point.Y > (maxY - overlayViewHeight))
{
if(isOverlayAboveAnchor)
if (isOverlayAboveAnchor)
{
// view out of bounds, hide overlay
point.X = -1;
@@ -453,7 +793,7 @@ namespace Bit.Droid.Accessibility
point.Y = -1;
}
}
else if(isOverlayAboveAnchor && point.Y < (maxY - overlayViewHeight - GetNodeHeight(anchorNode)))
else if (isOverlayAboveAnchor && point.Y < (maxY - (overlayViewHeight * 2) - GetNodeHeight(anchorNode)))
{
// This else block forces the overlay to return to bottom alignment as soon as space is available
// below the anchor view. Removing this will change the behavior to wait until there isn't enough
@@ -467,12 +807,12 @@ namespace Bit.Droid.Accessibility
public static bool IsStatusBarExpanded(IEnumerable<AccessibilityWindowInfo> windows)
{
if(windows != null && windows.Any())
if (windows != null && windows.Any())
{
var isSystemWindowsOnly = true;
foreach(var window in windows)
foreach (var window in windows)
{
if(window.Type != AccessibilityWindowType.System)
if (window.Type != AccessibilityWindowType.System)
{
isSystemWindowsOnly = false;
break;
@@ -483,32 +823,38 @@ namespace Bit.Droid.Accessibility
return false;
}
public static int GetApplicationVisibleHeight(IEnumerable<AccessibilityWindowInfo> windows)
public static int GetInputMethodHeight(IEnumerable<AccessibilityWindowInfo> windows)
{
var appWindowHeight = 0;
var nonAppWindowHeight = 0;
if(windows != null)
var inputMethodWindowHeight = 0;
if (windows != null)
{
foreach(var window in windows)
foreach (var window in windows)
{
var windowRect = new Rect();
window.GetBoundsInScreen(windowRect);
if(window.Type == AccessibilityWindowType.Application)
if (window.Type == AccessibilityWindowType.InputMethod)
{
appWindowHeight += windowRect.Height();
}
else
{
nonAppWindowHeight += windowRect.Height();
var windowRect = new Rect();
window.GetBoundsInScreen(windowRect);
inputMethodWindowHeight = windowRect.Height();
break;
}
}
}
return appWindowHeight - nonAppWindowHeight;
return inputMethodWindowHeight;
}
public static bool IsAutofillServicePromptVisible(IEnumerable<AccessibilityWindowInfo> windows)
{
// Autofill framework not available until API 26
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
return windows?.Any(w => w.Title?.ToLower().Contains("autofill") ?? false) ?? false;
}
return false;
}
public static int GetNodeHeight(AccessibilityNodeInfo node)
{
if(node == null)
if (node == null)
{
return -1;
}
@@ -519,26 +865,24 @@ namespace Bit.Droid.Accessibility
return nodeRectHeight;
}
private static int GetStatusBarHeight()
private static int GetStatusBarHeight(AccessibilityService service)
{
return GetSystemResourceDimenPx("status_bar_height");
return GetSystemResourceDimenPx(service, "status_bar_height");
}
private static int GetNavigationBarHeight()
private static int GetNavigationBarHeight(AccessibilityService service)
{
return GetSystemResourceDimenPx("navigation_bar_height");
return GetSystemResourceDimenPx(service, "navigation_bar_height");
}
private static int GetSystemResourceDimenPx(string resName)
private static int GetSystemResourceDimenPx(AccessibilityService service, string resName)
{
var activity = (MainActivity)CrossCurrentActivity.Current.Activity;
var barHeight = 0;
var resourceId = activity.Resources.GetIdentifier(resName, "dimen", "android");
if(resourceId > 0)
var resourceId = service.Resources.GetIdentifier(resName, "dimen", "android");
if (resourceId > 0)
{
barHeight = activity.Resources.GetDimensionPixelSize(resourceId);
return service.Resources.GetDimensionPixelSize(resourceId);
}
return barHeight;
return 0;
}
}
}

View File

@@ -4,9 +4,7 @@ using System.Linq;
using System.Threading.Tasks;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Provider;
using Android.Runtime;
using Android.Views;
using Android.Views.Accessibility;
@@ -15,7 +13,6 @@ using Bit.App.Resources;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Java.Util;
namespace Bit.Droid.Accessibility
{
@@ -29,6 +26,7 @@ namespace Bit.Droid.Accessibility
private const string BitwardenWebsite = "vault.bitwarden.com";
private IStorageService _storageService;
private IBroadcasterService _broadcasterService;
private DateTime? _lastSettingsReload = null;
private TimeSpan _settingsReloadSpan = TimeSpan.FromMinutes(1);
private HashSet<string> _blacklistedUris;
@@ -48,23 +46,45 @@ namespace Bit.Droid.Accessibility
private DateTime? _lastLauncherSetBuilt = null;
private TimeSpan _rebuildLauncherSpan = TimeSpan.FromHours(1);
public override void OnCreate()
{
base.OnCreate();
LoadServices();
var settingsTask = LoadSettingsAsync();
_broadcasterService.Subscribe(nameof(AccessibilityService), (message) =>
{
if (message.Command == "OnAutofillTileClick")
{
var runnable = new Java.Lang.Runnable(OnAutofillTileClick);
_handler.PostDelayed(runnable, 250);
}
});
AccessibilityHelpers.IsAccessibilityBroadcastReady = true;
}
public override void OnDestroy()
{
AccessibilityHelpers.IsAccessibilityBroadcastReady = false;
_broadcasterService.Unsubscribe(nameof(AccessibilityService));
}
public override void OnAccessibilityEvent(AccessibilityEvent e)
{
try
{
var powerManager = GetSystemService(PowerService) as PowerManager;
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
if (Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch && !powerManager.IsInteractive)
{
return;
}
else if(Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
else if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop && !powerManager.IsScreenOn)
{
return;
}
if(SkipPackage(e?.PackageName))
if (SkipPackage(e?.PackageName))
{
if(e?.PackageName != "com.android.systemui")
if (e?.PackageName != "com.android.systemui")
{
CancelOverlayPrompt();
}
@@ -77,28 +97,28 @@ namespace Bit.Droid.Accessibility
var settingsTask = LoadSettingsAsync();
AccessibilityNodeInfo root = null;
switch(e.EventType)
switch (e.EventType)
{
case EventTypes.ViewFocused:
case EventTypes.ViewClicked:
if(e.Source == null || e.PackageName == BitwardenPackage)
if (e.Source == null || e.PackageName == BitwardenPackage)
{
CancelOverlayPrompt();
break;
}
root = RootInActiveWindow;
if(root == null || root.PackageName != e.PackageName)
if (root == null || root.PackageName != e.PackageName)
{
break;
}
if(!(e.Source?.Password ?? false) && !AccessibilityHelpers.IsUsernameEditText(root, e))
if (!(e.Source?.Password ?? false) && !AccessibilityHelpers.IsUsernameEditText(root, e))
{
CancelOverlayPrompt();
break;
}
if(ScanAndAutofill(root, e))
if (ScanAndAutofill(root, e))
{
CancelOverlayPrompt();
}
@@ -109,22 +129,22 @@ namespace Bit.Droid.Accessibility
break;
case EventTypes.WindowContentChanged:
case EventTypes.WindowStateChanged:
if(AccessibilityHelpers.LastCredentials == null)
if (AccessibilityHelpers.LastCredentials == null)
{
break;
}
if(e.PackageName == BitwardenPackage)
if (e.PackageName == BitwardenPackage)
{
CancelOverlayPrompt();
break;
}
root = RootInActiveWindow;
if(root == null || root.PackageName != e.PackageName)
if (root == null || root.PackageName != e.PackageName)
{
break;
}
if(ScanAndAutofill(root, e))
if (ScanAndAutofill(root, e))
{
CancelOverlayPrompt();
}
@@ -134,7 +154,7 @@ namespace Bit.Droid.Accessibility
}
}
// Suppress exceptions so that service doesn't crash.
catch(Exception ex)
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(">>> {0}: {1}", ex.GetType(), ex.StackTrace);
}
@@ -148,22 +168,24 @@ namespace Bit.Droid.Accessibility
public bool ScanAndAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
{
var filled = false;
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
if(passwordNodes.Count > 0)
var uri = AccessibilityHelpers.GetUri(root);
if (uri != null && !uri.Contains(BitwardenWebsite) &&
AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
{
var uri = AccessibilityHelpers.GetUri(root);
if(uri != null && !uri.Contains(BitwardenWebsite))
var allEditTexts = AccessibilityHelpers.GetWindowNodes(root, e, n => AccessibilityHelpers.EditText(n), false);
var usernameEditText = AccessibilityHelpers.GetUsernameEditText(uri, allEditTexts);
var passwordNodes = AccessibilityHelpers.GetWindowNodes(root, e, n => n.Password, false);
if (usernameEditText != null || passwordNodes.Count > 0)
{
if(AccessibilityHelpers.NeedToAutofill(AccessibilityHelpers.LastCredentials, uri))
{
AccessibilityHelpers.GetNodesAndFill(root, e, passwordNodes);
filled = true;
_lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis();
}
AccessibilityHelpers.FillCredentials(usernameEditText, passwordNodes);
filled = true;
_lastAutoFillTime = Java.Lang.JavaSystem.CurrentTimeMillis();
AccessibilityHelpers.LastCredentials = null;
}
AccessibilityHelpers.LastCredentials = null;
allEditTexts.Dispose();
passwordNodes.Dispose();
}
else if(AccessibilityHelpers.LastCredentials != null)
if (AccessibilityHelpers.LastCredentials != null)
{
Task.Run(async () =>
{
@@ -171,18 +193,44 @@ namespace Bit.Droid.Accessibility
AccessibilityHelpers.LastCredentials = null;
});
}
passwordNodes.Dispose();
return filled;
}
private void OnAutofillTileClick()
{
CancelOverlayPrompt();
var root = RootInActiveWindow;
if (root != null && root.PackageName != BitwardenPackage &&
root.PackageName != AccessibilityHelpers.SystemUiPackage &&
!SkipPackage(root.PackageName))
{
var uri = AccessibilityHelpers.GetUri(root);
if (!string.IsNullOrWhiteSpace(uri))
{
var intent = new Intent(this, typeof(AccessibilityActivity));
intent.PutExtra("uri", uri);
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
StartActivity(intent);
return;
}
}
Toast.MakeText(this, AppResources.AutofillTileUriNotFound, ToastLength.Long).Show();
}
private void CancelOverlayPrompt()
{
_overlayAnchorObserverRunning = false;
if(_windowManager != null && _overlayView != null)
if (_windowManager != null && _overlayView != null)
{
_windowManager.RemoveViewImmediate(_overlayView);
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
try
{
_windowManager.RemoveViewImmediate(_overlayView);
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Removed");
}
catch { }
}
_overlayView = null;
@@ -190,7 +238,7 @@ namespace Bit.Droid.Accessibility
_lastAnchorY = 0;
_isOverlayAboveAnchor = false;
if(_anchorNode != null)
if (_anchorNode != null)
{
_anchorNode.Recycle();
_anchorNode = null;
@@ -199,30 +247,37 @@ namespace Bit.Droid.Accessibility
private void OverlayPromptToAutofill(AccessibilityNodeInfo root, AccessibilityEvent e)
{
if(!AccessibilityHelpers.OverlayPermitted())
if (Java.Lang.JavaSystem.CurrentTimeMillis() - _lastAutoFillTime < 1000 ||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
{
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityOverlayPermissionAlert, ToastLength.Long).Show();
return;
}
if (!AccessibilityHelpers.OverlayPermitted())
{
if (Build.VERSION.SdkInt <= BuildVersionCodes.M)
{
// The user has the option of only using the autofill tile and leaving the overlay permission
// disabled, so only show this toast if they're using accessibility without overlay permission on
// a version of Android without quick-action tile support
System.Diagnostics.Debug.WriteLine(">>> Overlay Permission not granted");
Toast.MakeText(this, AppResources.AccessibilityDrawOverPermissionAlert, ToastLength.Long).Show();
}
return;
}
if(_overlayView != null || _anchorNode != null || _overlayAnchorObserverRunning)
if (_overlayView != null || _anchorNode != null || _overlayAnchorObserverRunning)
{
CancelOverlayPrompt();
}
if(Java.Lang.JavaSystem.CurrentTimeMillis() - _lastAutoFillTime < 1000)
{
return;
}
var uri = AccessibilityHelpers.GetUri(root);
var fillable = !string.IsNullOrWhiteSpace(uri);
if(fillable)
if (fillable)
{
if(_blacklistedUris != null && _blacklistedUris.Any())
if (_blacklistedUris != null && _blacklistedUris.Any())
{
if(Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri) && parsedUri.Scheme.StartsWith("http"))
if (Uri.TryCreate(uri, UriKind.Absolute, out var parsedUri) && parsedUri.Scheme.StartsWith("http"))
{
fillable = !_blacklistedUris.Contains(
string.Format("{0}://{1}", parsedUri.Scheme, parsedUri.Host));
@@ -233,7 +288,7 @@ namespace Bit.Droid.Accessibility
}
}
}
if(!fillable)
if (!fillable)
{
return;
}
@@ -253,12 +308,12 @@ namespace Bit.Droid.Accessibility
};
var layoutParams = AccessibilityHelpers.GetOverlayLayoutParams();
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(e.Source, _overlayViewHeight,
_isOverlayAboveAnchor);
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, e.Source,
_overlayViewHeight, _isOverlayAboveAnchor);
layoutParams.X = anchorPosition.X;
layoutParams.Y = anchorPosition.Y;
if(_windowManager == null)
if (_windowManager == null)
{
_windowManager = GetSystemService(WindowService).JavaCast<IWindowManager>();
}
@@ -277,7 +332,7 @@ namespace Bit.Droid.Accessibility
private void StartOverlayAnchorObserver()
{
if(_overlayAnchorObserverRunning)
if (_overlayAnchorObserverRunning)
{
return;
}
@@ -285,7 +340,7 @@ namespace Bit.Droid.Accessibility
_overlayAnchorObserverRunning = true;
_overlayAnchorObserverRunnable = new Java.Lang.Runnable(() =>
{
if(_overlayAnchorObserverRunning)
if (_overlayAnchorObserverRunning)
{
AdjustOverlayForScroll();
_handler.PostDelayed(_overlayAnchorObserverRunnable, 250);
@@ -297,7 +352,8 @@ namespace Bit.Droid.Accessibility
private void AdjustOverlayForScroll()
{
if(_overlayView == null || _anchorNode == null)
if (_overlayView == null || _anchorNode == null ||
AccessibilityHelpers.IsAutofillServicePromptVisible(Windows))
{
CancelOverlayPrompt();
return;
@@ -305,42 +361,42 @@ namespace Bit.Droid.Accessibility
var root = RootInActiveWindow;
IEnumerable<AccessibilityWindowInfo> windows = null;
if(Build.VERSION.SdkInt > BuildVersionCodes.Kitkat)
if (Build.VERSION.SdkInt > BuildVersionCodes.Kitkat)
{
windows = Windows;
}
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(_anchorNode, root, windows,
_overlayViewHeight, _isOverlayAboveAnchor);
if(anchorPosition == null)
var anchorPosition = AccessibilityHelpers.GetOverlayAnchorPosition(this, _anchorNode, root,
windows, _overlayViewHeight, _isOverlayAboveAnchor);
if (anchorPosition == null)
{
CancelOverlayPrompt();
return;
}
else if(anchorPosition.X == -1 && anchorPosition.Y == -1)
else if (anchorPosition.X == -1 && anchorPosition.Y == -1)
{
if(_overlayView.Visibility != ViewStates.Gone)
if (_overlayView.Visibility != ViewStates.Gone)
{
_overlayView.Visibility = ViewStates.Gone;
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Hidden");
}
return;
}
else if(anchorPosition.X == -1)
else if (anchorPosition.X == -1)
{
_isOverlayAboveAnchor = false;
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Below Anchor");
return;
}
else if(anchorPosition.Y == -1)
else if (anchorPosition.Y == -1)
{
_isOverlayAboveAnchor = true;
System.Diagnostics.Debug.WriteLine(">>> Accessibility Overlay View Above Anchor");
return;
}
else if(anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY)
else if (anchorPosition.X == _lastAnchorX && anchorPosition.Y == _lastAnchorY)
{
if(_overlayView.Visibility != ViewStates.Visible)
if (_overlayView.Visibility != ViewStates.Visible)
{
_overlayView.Visibility = ViewStates.Visible;
}
@@ -356,7 +412,7 @@ namespace Bit.Droid.Accessibility
_windowManager.UpdateViewLayout(_overlayView, layoutParams);
if(_overlayView.Visibility != ViewStates.Visible)
if (_overlayView.Visibility != ViewStates.Visible)
{
_overlayView.Visibility = ViewStates.Visible;
}
@@ -367,13 +423,13 @@ namespace Bit.Droid.Accessibility
private bool SkipPackage(string eventPackageName)
{
if(string.IsNullOrWhiteSpace(eventPackageName) ||
if (string.IsNullOrWhiteSpace(eventPackageName) ||
AccessibilityHelpers.FilteredPackageNames.Contains(eventPackageName) ||
eventPackageName.Contains("launcher"))
{
return true;
}
if(_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
if (_launcherPackageNames == null || _lastLauncherSetBuilt == null ||
(DateTime.Now - _lastLauncherSetBuilt.Value) > _rebuildLauncherSpan)
{
// refresh launcher list every now and then
@@ -388,23 +444,29 @@ namespace Bit.Droid.Accessibility
private void LoadServices()
{
if(_storageService == null)
if (_storageService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
}
if (_broadcasterService == null)
{
_broadcasterService = ServiceContainer.Resolve<IBroadcasterService>("broadcasterService");
}
}
private async Task LoadSettingsAsync()
{
var now = DateTime.UtcNow;
if(_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
if (_lastSettingsReload == null || (now - _lastSettingsReload.Value) > _settingsReloadSpan)
{
_lastSettingsReload = now;
var uris = await _storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
if(uris != null)
if (uris != null)
{
_blacklistedUris = new HashSet<string>(uris);
}
var isAutoFillTileAdded = await _storageService.GetAsync<bool?>(Constants.AutofillTileAdded);
AccessibilityHelpers.IsAutofillTileAdded = isAutoFillTileAdded.GetValueOrDefault();
}
}
}

View File

@@ -0,0 +1,14 @@
namespace Bit.Droid.Accessibility
{
public class KnownUsernameField
{
public KnownUsernameField(string uriAuthority, (string UriPathWanted, string UsernameViewId)[] accessOptions)
{
UriAuthority = uriAuthority;
AccessOptions = accessOptions;
}
public string UriAuthority { get; set; }
public (string UriPathWanted, string UsernameViewId)[] AccessOptions { get; set; }
}
}

View File

@@ -8,7 +8,7 @@ namespace Bit.Droid.Accessibility
{
public void Dispose()
{
foreach(var item in this)
foreach (var item in this)
{
item.Recycle();
item.Dispose();

View File

@@ -15,8 +15,7 @@
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
<MonoAndroidResourcePrefix>Resources</MonoAndroidResourcePrefix>
<MonoAndroidAssetsPrefix>Assets</MonoAndroidAssetsPrefix>
<AndroidUseLatestPlatformSdk>false</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v11.0</TargetFrameworkVersion>
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidClientHandler</AndroidHttpClientHandlerType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
@@ -30,10 +29,10 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>3</WarningLevel>
<AndroidLinkMode>None</AndroidLinkMode>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
<AndroidSupportedAbis />
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net;Xamarin.Android.Net.OldAndroidSSLSocketFactory</AndroidLinkSkip>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugSymbols>false</DebugSymbols>
@@ -46,10 +45,8 @@
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net;Xamarin.Android.Net.OldAndroidSSLSocketFactory</AndroidLinkSkip>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidLinkMode>Full</AndroidLinkMode>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'FDroid|AnyCPU'">
<DebugSymbols>false</DebugSymbols>
@@ -63,11 +60,9 @@
<DefineConstants>FDROID</DefineConstants>
<AndroidSupportedAbis>armeabi-v7a;x86;x86_64;arm64-v8a</AndroidSupportedAbis>
<JavaMaximumHeapSize>1G</JavaMaximumHeapSize>
<AndroidEnableMultiDex>true</AndroidEnableMultiDex>
<AndroidUseSharedRuntime>false</AndroidUseSharedRuntime>
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
<AndroidLinkSkip>Xamarin.GooglePlayServices.Base;Xamarin.GooglePlayServices.Basement;Xamarin.GooglePlayServices.Measurement;Xamarin.GooglePlayServices.Gcm;BitwardenAndroid;BitwardenApp;BitwardenCore;Xamarin.Android.Net;Xamarin.Android.Net.OldAndroidSSLSocketFactory</AndroidLinkSkip>
<AndroidLinkMode>Full</AndroidLinkMode>
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
@@ -82,21 +77,23 @@
<Version>2.1.0.4</Version>
</PackageReference>
<PackageReference Include="Portable.BouncyCastle">
<Version>1.8.5.2</Version>
<Version>1.8.6.7</Version>
</PackageReference>
<PackageReference Include="Xamarin.AndroidX.AutoFill" Version="1.1.0.3-beta01" />
<PackageReference Include="Xamarin.Essentials">
<Version>1.3.1</Version>
<Version>1.5.3.2</Version>
</PackageReference>
<PackageReference Include="Xamarin.Firebase.Messaging">
<Version>60.1142.1</Version>
<Version>71.1740.0</Version>
</PackageReference>
<PackageReference Include="Xamarin.Android.Support.Design" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.AppCompat" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v4" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.CardView" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Android.Support.v7.MediaRouter" Version="28.0.0.3" />
<PackageReference Include="Xamarin.Google.Android.Material" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.AppCompat" Version="1.1.0" />
<PackageReference Include="Xamarin.AndroidX.Legacy.Support.V4" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.CardView" Version="1.0.0" />
<PackageReference Include="Xamarin.AndroidX.MediaRouter" Version="1.1.0" />
<PackageReference Include="Xamarin.AndroidX.Migration" Version="1.0.6" />
<PackageReference Include="Xamarin.GooglePlayServices.SafetyNet">
<Version>60.1142.1</Version>
<Version>71.1600.0</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
@@ -106,6 +103,7 @@
<Compile Include="Accessibility\AccessibilityService.cs" />
<Compile Include="Accessibility\Browser.cs" />
<Compile Include="Accessibility\NodeList.cs" />
<Compile Include="Accessibility\KnownUsernameField.cs" />
<Compile Include="Autofill\AutofillHelpers.cs" />
<Compile Include="Autofill\AutofillService.cs" />
<Compile Include="Autofill\Field.cs" />
@@ -113,6 +111,7 @@
<Compile Include="Autofill\FilledItem.cs" />
<Compile Include="Autofill\Parser.cs" />
<Compile Include="Autofill\SavedItem.cs" />
<Compile Include="Effects\FabShadowEffect.cs" />
<Compile Include="Effects\FixedSizeEffect.cs" />
<Compile Include="Effects\SelectableLabelEffect.cs" />
<Compile Include="Effects\TabBarEffect.cs" />
@@ -124,6 +123,7 @@
<Compile Include="Receivers\LockAlarmReceiver.cs" />
<Compile Include="Receivers\PackageReplacedReceiver.cs" />
<Compile Include="Renderers\CipherViewCellRenderer.cs" />
<Compile Include="Renderers\CustomTabbedRenderer.cs" />
<Compile Include="Renderers\ExtendedSliderRenderer.cs" />
<Compile Include="Renderers\CustomEditorRenderer.cs" />
<Compile Include="Renderers\CustomPickerRenderer.cs" />
@@ -137,15 +137,16 @@
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Services\BiometricService.cs" />
<Compile Include="Services\CryptoPrimitiveService.cs" />
<Compile Include="Services\DeviceActionService.cs" />
<Compile Include="Services\LocalizeService.cs" />
<Compile Include="Tiles\AutofillTileService.cs" />
<Compile Include="Tiles\GeneratorTileService.cs" />
<Compile Include="Tiles\MyVaultTileService.cs" />
<Compile Include="Utilities\AndroidHelpers.cs" />
<Compile Include="Utilities\CustomFingerprintDialogFragment.cs" />
<Compile Include="Utilities\HockeyAppCrashManagerListener.cs" />
<Compile Include="Utilities\StaticStore.cs" />
<Compile Include="Utilities\AppCenterHelper.cs" />
<Compile Include="WebAuthCallbackActivity.cs" />
</ItemGroup>
<ItemGroup>
<AndroidAsset Include="Assets\FontAwesome.ttf" />
@@ -155,11 +156,48 @@
<None Include="ci-build-apks.ps1" />
<GoogleServicesJson Include="google-services.json" />
<GoogleServicesJson Include="google-services.json.enc" />
<None Include="fdroid-keystore.jks.enc" />
<None Include="Properties\AndroidManifest.xml" />
<None Include="upload-keystore.jks.enc" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-hdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable-xhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_legacy.png" />
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white_legacy.png" />
<AndroidResource Include="Resources\drawable\card.xml" />
<AndroidResource Include="Resources\drawable\cog.xml" />
<AndroidResource Include="Resources\drawable\icon.xml" />
<AndroidResource Include="Resources\drawable\ic_launcher_foreground.xml" />
<AndroidResource Include="Resources\drawable\id.xml" />
<AndroidResource Include="Resources\drawable\lock.xml" />
<AndroidResource Include="Resources\drawable\login.xml" />
<AndroidResource Include="Resources\drawable\logo.xml" />
<AndroidResource Include="Resources\drawable\logo_white.xml" />
<AndroidResource Include="Resources\drawable\pencil.xml" />
<AndroidResource Include="Resources\drawable\plus.xml" />
<AndroidResource Include="Resources\drawable\refresh.xml" />
<AndroidResource Include="Resources\drawable\search.xml" />
<AndroidResource Include="Resources\drawable\shield.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen.xml" />
<AndroidResource Include="Resources\drawable-v23\splash_screen_dark.xml" />
<AndroidResource Include="Resources\layout\Tabbar.axml" />
<AndroidResource Include="Resources\layout\Toolbar.axml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher_round.xml" />
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher.png" />
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
<AndroidResource Include="Resources\values-night\styles.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
</ItemGroup>
@@ -194,261 +232,18 @@
<ItemGroup>
<AndroidResource Include="Resources\values\ic_launcher_background.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxxhdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-anydpi-v26\ic_launcher_round.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-hdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-mdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xhdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_foreground.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\mipmap-xxhdpi\ic_launcher_round.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\autofill_listitem.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\login.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\login.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\login.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\login.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\login.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\logo.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values-v21\styles.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\refresh.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\logo.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\shield.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\autofill_use.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\autofill_enable.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\autofill_use.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\autofill_enable.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\autofill_enable.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\autofill_use.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_use.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\logo.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\yubikey.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\autofill_enable.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\refresh.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\refresh.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\refresh.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\lock.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\refresh.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\id.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\card.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\id.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\card.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\id.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\card.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\id.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\card.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\card.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\id.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\pencil.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\plus.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\pencil.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\plus.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\pencil.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\plus.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\pencil.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\plus.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\plus.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\pencil.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\slider_thumb.xml" />
</ItemGroup>
@@ -458,102 +253,18 @@
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\logo_white.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\logo_white.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\logo_white.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\search.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\cog.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\cog.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\cog.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\cog.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\cog.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\dimens.xml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\shield.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\shield.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\shield.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\shield.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\refresh_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\refresh_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\refresh_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\refresh_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\refresh_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\CipherViewCell.axml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>
<SubType>Designer</SubType>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_overlay.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_permission.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_permission.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_permission.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\xml\app_restrictions.xml">
<Generator>MSBuild:UpdateGeneratedFiles</Generator>

View File

@@ -1,14 +1,24 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Android.Content;
using Android.Service.Autofill;
using Android.Widget;
using System.Linq;
using Android.App;
using System.Threading.Tasks;
using Android.App.Slices;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Widget.Inline;
using Bit.App.Resources;
using Bit.Core.Enums;
using Android.Views.Autofill;
using AndroidX.AutoFill.Inline;
using AndroidX.AutoFill.Inline.V1;
using Bit.Core.Abstractions;
using SaveFlags = Android.Service.Autofill.SaveFlags;
namespace Bit.Droid.Autofill
{
@@ -16,52 +26,91 @@ namespace Bit.Droid.Autofill
{
private static int _pendingIntentId = 0;
// These browser work natively with the autofill framework
// These browsers work natively with the Autofill Framework
//
// Be sure:
// - to keep these entries sorted alphabetically and
//
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> TrustedBrowsers = new HashSet<string>
{
"com.duckduckgo.mobile.android",
"org.mozilla.focus",
"org.mozilla.klar",
"com.duckduckgo.mobile.android",
};
// These browsers work using the compatibility shim for the autofill framework
// These browsers work using the compatibility shim for the Autofill Framework
//
// Be sure:
// - to keep these entries sorted alphabetically,
// - to keep this list in sync with values in Resources/xml/autofillservice.xml, and
//
// - ... to keep this list in sync with values in AccessibilityHelpers.SupportedBrowsers [Section A], too.
public static HashSet<string> CompatBrowsers = new HashSet<string>
{
"org.mozilla.firefox",
"org.mozilla.firefox_beta",
"com.microsoft.emmx",
"com.android.chrome",
"com.chrome.beta",
"com.amazon.cloud9",
"com.android.browser",
"com.android.chrome",
"com.android.htmlviewer",
"com.avast.android.secure.browser",
"com.avg.android.secure.browser",
"com.brave.browser",
"com.brave.browser_beta",
"com.brave.browser_default",
"com.brave.browser_dev",
"com.brave.browser_nightly",
"com.chrome.beta",
"com.chrome.canary",
"com.chrome.dev",
"com.ecosia.android",
"com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev",
"com.kiwibrowser.browser",
"com.microsoft.emmx",
"com.mmbox.browser",
"com.mmbox.xbrowser",
"com.naver.whale",
"com.opera.browser",
"com.opera.browser.beta",
"com.opera.mini.native",
"com.chrome.dev",
"com.chrome.canary",
"com.google.android.apps.chrome",
"com.google.android.apps.chrome_dev",
"com.yandex.browser",
"com.opera.mini.native.beta",
"com.opera.touch",
"com.qwant.liberty",
"com.sec.android.app.sbrowser",
"com.sec.android.app.sbrowser.beta",
"org.codeaurora.swe.browser",
"com.amazon.cloud9",
"com.stoutner.privacybrowser.free",
"com.stoutner.privacybrowser.standard",
"com.vivaldi.browser",
"com.vivaldi.browser.snapshot",
"com.vivaldi.browser.sopranos",
"com.yandex.browser",
"com.z28j.feel",
"idm.internet.download.manager",
"idm.internet.download.manager.adm.lite",
"idm.internet.download.manager.plus",
"io.github.forkmaintainers.iceraven",
"mark.via",
"mark.via.gp",
"org.adblockplus.browser",
"org.adblockplus.browser.beta",
"org.bromite.bromite",
"org.bromite.chromium",
"org.chromium.chrome",
"com.kiwibrowser.browser",
"com.ecosia.android",
"com.opera.mini.native.beta",
"org.mozilla.fennec_aurora",
"org.mozilla.fennec_fdroid",
"com.qwant.liberty",
"com.opera.touch",
"org.codeaurora.swe.browser",
"org.gnu.icecat",
"org.mozilla.fenix",
"org.mozilla.fenix.nightly",
"org.mozilla.fennec_aurora",
"org.mozilla.fennec_fdroid",
"org.mozilla.firefox",
"org.mozilla.firefox_beta",
"org.mozilla.reference.browser",
"org.mozilla.rocket",
"org.torproject.torbrowser",
"com.vivaldi.browser",
"org.torproject.torbrowser_alpha",
"org.ungoogled.chromium",
"org.ungoogled.chromium.extensions.stable",
"org.ungoogled.chromium.stable",
};
// The URLs are blacklisted from autofilling
@@ -75,17 +124,17 @@ namespace Bit.Droid.Autofill
public static async Task<List<FilledItem>> GetFillItemsAsync(Parser parser, ICipherService cipherService)
{
if(parser.FieldCollection.FillableForLogin)
if (parser.FieldCollection.FillableForLogin)
{
var ciphers = await cipherService.GetAllDecryptedByUrlAsync(parser.Uri);
if(ciphers.Item1.Any() || ciphers.Item2.Any())
if (ciphers.Item1.Any() || ciphers.Item2.Any())
{
var allCiphers = ciphers.Item1.ToList();
allCiphers.AddRange(ciphers.Item2.ToList());
return allCiphers.Select(c => new FilledItem(c)).ToList();
}
}
else if(parser.FieldCollection.FillableForCard)
else if (parser.FieldCollection.FillableForCard)
{
var ciphers = await cipherService.GetAllDecryptedAsync();
return ciphers.Where(c => c.Type == CipherType.Card).Select(c => new FilledItem(c)).ToList();
@@ -93,51 +142,105 @@ namespace Bit.Droid.Autofill
return new List<FilledItem>();
}
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked)
public static FillResponse BuildFillResponse(Parser parser, List<FilledItem> items, bool locked,
bool inlineAutofillEnabled, FillRequest fillRequest = null)
{
var responseBuilder = new FillResponse.Builder();
if(items != null && items.Count > 0)
// Acquire inline presentation specs on Android 11+
IList<InlinePresentationSpec> inlinePresentationSpecs = null;
var inlinePresentationSpecsCount = 0;
var inlineMaxSuggestedCount = 0;
if (inlineAutofillEnabled && fillRequest != null && (int)Build.VERSION.SdkInt >= 30)
{
foreach(var item in items)
var inlineSuggestionsRequest = fillRequest.InlineSuggestionsRequest;
inlineMaxSuggestedCount = inlineSuggestionsRequest?.MaxSuggestionCount ?? 0;
inlinePresentationSpecs = inlineSuggestionsRequest?.InlinePresentationSpecs;
inlinePresentationSpecsCount = inlinePresentationSpecs?.Count ?? 0;
}
// Build response
var responseBuilder = new FillResponse.Builder();
if (items != null && items.Count > 0)
{
var maxItems = items.Count;
if (inlineMaxSuggestedCount > 0)
{
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, item);
if(dataset != null)
// -1 to adjust for 'open vault' option
maxItems = Math.Min(maxItems, inlineMaxSuggestedCount - 1);
}
for (int i = 0; i < maxItems; i++)
{
InlinePresentationSpec inlinePresentationSpec = null;
if (inlinePresentationSpecs != null)
{
if (i < inlinePresentationSpecsCount)
{
inlinePresentationSpec = inlinePresentationSpecs[i];
}
else
{
// If the max suggestion count is larger than the number of specs in the list, then
// the last spec is used for the remainder of the suggestions
inlinePresentationSpec = inlinePresentationSpecs[inlinePresentationSpecsCount - 1];
}
}
var dataset = BuildDataset(parser.ApplicationContext, parser.FieldCollection, items[i],
inlinePresentationSpec);
if (dataset != null)
{
responseBuilder.AddDataset(dataset);
}
}
}
responseBuilder.AddDataset(BuildVaultDataset(parser.ApplicationContext, parser.FieldCollection,
parser.Uri, locked));
AddSaveInfo(parser, responseBuilder, parser.FieldCollection);
parser.Uri, locked, inlinePresentationSpecs));
AddSaveInfo(parser, fillRequest, responseBuilder, parser.FieldCollection);
responseBuilder.SetIgnoredIds(parser.FieldCollection.IgnoreAutofillIds.ToArray());
return responseBuilder.Build();
}
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem)
public static Dataset BuildDataset(Context context, FieldCollection fields, FilledItem filledItem,
InlinePresentationSpec inlinePresentationSpec = null)
{
var datasetBuilder = new Dataset.Builder(
BuildListView(filledItem.Name, filledItem.Subtitle, filledItem.Icon, context));
if(filledItem.ApplyToFields(fields, datasetBuilder))
var overlayPresentation = BuildOverlayPresentation(
filledItem.Name,
filledItem.Subtitle,
filledItem.Icon,
context);
var inlinePresentation = BuildInlinePresentation(
inlinePresentationSpec,
filledItem.Name,
filledItem.Subtitle,
filledItem.Icon,
null,
context);
var datasetBuilder = new Dataset.Builder(overlayPresentation);
if (inlinePresentation != null)
{
datasetBuilder.SetInlinePresentation(inlinePresentation);
}
if (filledItem.ApplyToFields(fields, datasetBuilder))
{
return datasetBuilder.Build();
}
return null;
}
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked)
public static Dataset BuildVaultDataset(Context context, FieldCollection fields, string uri, bool locked,
IList<InlinePresentationSpec> inlinePresentationSpecs = null)
{
var intent = new Intent(context, typeof(MainActivity));
intent.PutExtra("autofillFramework", true);
if(fields.FillableForLogin)
if (fields.FillableForLogin)
{
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Login);
}
else if(fields.FillableForCard)
else if (fields.FillableForCard)
{
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Card);
}
else if(fields.FillableForIdentity)
else if (fields.FillableForIdentity)
{
intent.PutExtra("autofillFrameworkFillType", (int)CipherType.Identity);
}
@@ -149,24 +252,36 @@ namespace Bit.Droid.Autofill
var pendingIntent = PendingIntent.GetActivity(context, ++_pendingIntentId, intent,
PendingIntentFlags.CancelCurrent);
var view = BuildListView(
var overlayPresentation = BuildOverlayPresentation(
AppResources.AutofillWithBitwarden,
locked ? AppResources.VaultIsLocked : AppResources.GoToMyVault,
Resource.Drawable.icon,
context);
var datasetBuilder = new Dataset.Builder(view);
datasetBuilder.SetAuthentication(pendingIntent.IntentSender);
var inlinePresentation = BuildInlinePresentation(
inlinePresentationSpecs?.Last(),
AppResources.Bitwarden,
locked ? AppResources.VaultIsLocked : AppResources.MyVault,
Resource.Drawable.icon,
pendingIntent,
context);
var datasetBuilder = new Dataset.Builder(overlayPresentation);
if (inlinePresentation != null)
{
datasetBuilder.SetInlinePresentation(inlinePresentation);
}
datasetBuilder.SetAuthentication(pendingIntent?.IntentSender);
// Dataset must have a value set. We will reset this in the main activity when the real item is chosen.
foreach(var autofillId in fields.AutofillIds)
foreach (var autofillId in fields.AutofillIds)
{
datasetBuilder.SetValue(autofillId, AutofillValue.ForText("PLACEHOLDER"));
}
return datasetBuilder.Build();
}
public static RemoteViews BuildListView(string text, string subtext, int iconId, Context context)
public static RemoteViews BuildOverlayPresentation(string text, string subtext, int iconId, Context context)
{
var packageName = context.PackageName;
var view = new RemoteViews(packageName, Resource.Layout.autofill_listitem);
@@ -176,29 +291,105 @@ namespace Bit.Droid.Autofill
return view;
}
public static void AddSaveInfo(Parser parser, FillResponse.Builder responseBuilder, FieldCollection fields)
public static InlinePresentation BuildInlinePresentation(InlinePresentationSpec inlinePresentationSpec,
string text, string subtext, int iconId, PendingIntent pendingIntent, Context context)
{
if ((int)Build.VERSION.SdkInt < 30 || inlinePresentationSpec == null)
{
return null;
}
if (pendingIntent == null)
{
// 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);
}
var slice = CreateInlinePresentationSlice(
inlinePresentationSpec,
text,
subtext,
iconId,
"Autofill option",
pendingIntent,
context);
if (slice != null)
{
return new InlinePresentation(slice, inlinePresentationSpec, false);
}
return null;
}
private static Slice CreateInlinePresentationSlice(
InlinePresentationSpec inlinePresentationSpec,
string text,
string subtext,
int iconId,
string contentDescription,
PendingIntent pendingIntent,
Context context)
{
var imeStyle = inlinePresentationSpec.Style;
if (!UiVersions.GetVersions(imeStyle).Contains(UiVersions.InlineUiVersion1))
{
return null;
}
var contentBuilder = InlineSuggestionUi.NewContentBuilder(pendingIntent)
.SetContentDescription(contentDescription);
if (!string.IsNullOrWhiteSpace(text))
{
contentBuilder.SetTitle(text);
}
if (!string.IsNullOrWhiteSpace(subtext))
{
contentBuilder.SetSubtitle(subtext);
}
if (iconId > 0)
{
var icon = Icon.CreateWithResource(context, iconId);
if (icon != null)
{
if (iconId == Resource.Drawable.icon)
{
// Don't tint our logo
icon.SetTintBlendMode(BlendMode.Dst);
}
contentBuilder.SetStartIcon(icon);
}
}
return contentBuilder.Build().JavaCast<InlineSuggestionUi.Content>()?.Slice;
}
public static void AddSaveInfo(Parser parser, FillRequest fillRequest, FillResponse.Builder responseBuilder,
FieldCollection fields)
{
// Docs state that password fields cannot be reliably saved in Compat mode since they will show as
// masked values.
var compatBrowser = CompatBrowsers.Contains(parser.PackageName);
if(compatBrowser && fields.SaveType == SaveDataType.Password)
bool? compatRequest = null;
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q && fillRequest != null)
{
// Attempt to automatically establish compat request mode on Android 10+
compatRequest = (fillRequest.Flags | FillRequest.FlagCompatibilityModeRequest) == fillRequest.Flags;
}
var compatBrowser = compatRequest ?? CompatBrowsers.Contains(parser.PackageName);
if (compatBrowser && fields.SaveType == SaveDataType.Password)
{
return;
}
var requiredIds = fields.GetRequiredSaveFields();
if(fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
if (fields.SaveType == SaveDataType.Generic || requiredIds.Length == 0)
{
return;
}
var saveBuilder = new SaveInfo.Builder(fields.SaveType, requiredIds);
var optionalIds = fields.GetOptionalSaveIds();
if(optionalIds.Length > 0)
if (optionalIds.Length > 0)
{
saveBuilder.SetOptionalIds(optionalIds);
}
if(compatBrowser)
if (compatBrowser)
{
saveBuilder.SetFlags(SaveFlags.SaveOnAllViewsInvisible);
}

View File

@@ -21,14 +21,14 @@ namespace Bit.Droid.Autofill
public class AutofillService : Android.Service.Autofill.AutofillService
{
private ICipherService _cipherService;
private ILockService _lockService;
private IVaultTimeoutService _vaultTimeoutService;
private IStorageService _storageService;
public async override void OnFillRequest(FillRequest request, CancellationSignal cancellationSignal,
FillCallback callback)
{
var structure = request.FillContexts?.LastOrDefault()?.Structure;
if(structure == null)
if (structure == null)
{
return;
}
@@ -36,27 +36,29 @@ namespace Bit.Droid.Autofill
var parser = new Parser(structure, ApplicationContext);
parser.Parse();
if(_storageService == null)
if (_storageService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
}
var shouldAutofill = await parser.ShouldAutofillAsync(_storageService);
if(!shouldAutofill)
if (!shouldAutofill)
{
return;
}
var inlineAutofillEnabled = await _storageService.GetAsync<bool?>(Constants.InlineAutofillEnabledKey) ?? true;
if(_lockService == null)
if (_vaultTimeoutService == null)
{
_lockService = ServiceContainer.Resolve<ILockService>("lockService");
_vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
}
List<FilledItem> items = null;
var locked = await _lockService.IsLockedAsync();
if(!locked)
var locked = await _vaultTimeoutService.IsLockedAsync();
if (!locked)
{
if(_cipherService == null)
if (_cipherService == null)
{
_cipherService = ServiceContainer.Resolve<ICipherService>("cipherService");
}
@@ -64,25 +66,25 @@ namespace Bit.Droid.Autofill
}
// build response
var response = AutofillHelpers.BuildFillResponse(parser, items, locked);
var response = AutofillHelpers.BuildFillResponse(parser, items, locked, inlineAutofillEnabled, request);
callback.OnSuccess(response);
}
public async override void OnSaveRequest(SaveRequest request, SaveCallback callback)
{
var structure = request.FillContexts?.LastOrDefault()?.Structure;
if(structure == null)
if (structure == null)
{
return;
}
if(_storageService == null)
if (_storageService == null)
{
_storageService = ServiceContainer.Resolve<IStorageService>("storageService");
}
var disableSavePrompt = await _storageService.GetAsync<bool?>(Constants.AutofillDisableSavePromptKey);
if(disableSavePrompt.GetValueOrDefault())
if (disableSavePrompt.GetValueOrDefault())
{
return;
}
@@ -91,7 +93,7 @@ namespace Bit.Droid.Autofill
parser.Parse();
var savedItem = parser.FieldCollection.GetSavedItem();
if(savedItem == null)
if (savedItem == null)
{
Toast.MakeText(this, "Unable to save this form.", ToastLength.Short).Show();
return;
@@ -102,7 +104,7 @@ namespace Bit.Droid.Autofill
intent.PutExtra("autofillFramework", true);
intent.PutExtra("autofillFrameworkSave", true);
intent.PutExtra("autofillFrameworkType", (int)savedItem.Type);
switch(savedItem.Type)
switch (savedItem.Type)
{
case CipherType.Login:
intent.PutExtra("autofillFrameworkName", parser.Uri

View File

@@ -31,26 +31,26 @@ namespace Bit.Droid.Autofill
HtmlInfo = node.HtmlInfo;
Node = node;
if(node.AutofillValue != null)
if (node.AutofillValue != null)
{
if(node.AutofillValue.IsList)
if (node.AutofillValue.IsList)
{
var autofillOptions = node.GetAutofillOptions();
if(autofillOptions != null && autofillOptions.Length > 0)
if (autofillOptions != null && autofillOptions.Length > 0)
{
ListValue = node.AutofillValue.ListValue;
TextValue = autofillOptions[node.AutofillValue.ListValue];
}
}
else if(node.AutofillValue.IsDate)
else if (node.AutofillValue.IsDate)
{
DateValue = node.AutofillValue.DateValue;
}
else if(node.AutofillValue.IsText)
else if (node.AutofillValue.IsText)
{
TextValue = node.AutofillValue.TextValue;
}
else if(node.AutofillValue.IsToggle)
else if (node.AutofillValue.IsToggle)
{
ToggleValue = node.AutofillValue.ToggleValue;
}
@@ -93,20 +93,20 @@ namespace Bit.Droid.Autofill
public override bool Equals(object obj)
{
if(this == obj)
if (this == obj)
{
return true;
}
if(obj == null || GetType() != obj.GetType())
if (obj == null || GetType() != obj.GetType())
{
return false;
}
var field = obj as Field;
if(TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
if (TextValue != null ? !TextValue.Equals(field.TextValue) : field.TextValue != null)
{
return false;
}
if(DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
if (DateValue != null ? !DateValue.Equals(field.DateValue) : field.DateValue != null)
{
return false;
}
@@ -128,7 +128,7 @@ namespace Bit.Droid.Autofill
private static bool IsValidHint(string hint)
{
switch(hint)
switch (hint)
{
case View.AutofillHintCreditCardExpirationDate:
case View.AutofillHintCreditCardExpirationDay:
@@ -152,14 +152,14 @@ namespace Bit.Droid.Autofill
private void UpdateSaveTypeFromHints()
{
SaveType = SaveDataType.Generic;
if(_hints == null)
if (_hints == null)
{
return;
}
foreach(var hint in _hints)
foreach (var hint in _hints)
{
switch(hint)
switch (hint)
{
case View.AutofillHintCreditCardExpirationDate:
case View.AutofillHintCreditCardExpirationDay:

View File

@@ -19,11 +19,11 @@ namespace Bit.Droid.Autofill
{
get
{
if(FillableForLogin)
if (FillableForLogin)
{
return SaveDataType.Password;
}
else if(FillableForCard)
else if (FillableForCard)
{
return SaveDataType.CreditCard;
}
@@ -43,14 +43,14 @@ namespace Bit.Droid.Autofill
{
get
{
if(_passwordFields != null)
if (_passwordFields != null)
{
return _passwordFields;
}
if(Hints.Any())
if (Hints.Any())
{
_passwordFields = new List<Field>();
if(HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
if (HintToFieldsMap.ContainsKey(View.AutofillHintPassword))
{
_passwordFields.AddRange(HintToFieldsMap[View.AutofillHintPassword]);
}
@@ -58,7 +58,7 @@ namespace Bit.Droid.Autofill
else
{
_passwordFields = Fields.Where(f => FieldIsPassword(f)).ToList();
if(!_passwordFields.Any())
if (!_passwordFields.Any())
{
_passwordFields = Fields.Where(f => FieldHasPasswordTerms(f)).ToList();
}
@@ -71,29 +71,29 @@ namespace Bit.Droid.Autofill
{
get
{
if(_usernameFields != null)
if (_usernameFields != null)
{
return _usernameFields;
}
_usernameFields = new List<Field>();
if(Hints.Any())
if (Hints.Any())
{
if(HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
if (HintToFieldsMap.ContainsKey(View.AutofillHintEmailAddress))
{
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintEmailAddress]);
}
if(HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
if (HintToFieldsMap.ContainsKey(View.AutofillHintUsername))
{
_usernameFields.AddRange(HintToFieldsMap[View.AutofillHintUsername]);
}
}
else
{
foreach(var passwordField in PasswordFields)
foreach (var passwordField in PasswordFields)
{
var usernameField = Fields.TakeWhile(f => f.AutofillId != passwordField.AutofillId)
.LastOrDefault();
if(usernameField != null)
if (usernameField != null)
{
_usernameFields.Add(usernameField);
}
@@ -127,7 +127,7 @@ namespace Bit.Droid.Autofill
public void Add(Field field)
{
if(field == null || FieldTrackingIds.Contains(field.TrackingId))
if (field == null || FieldTrackingIds.Contains(field.TrackingId))
{
return;
}
@@ -137,16 +137,16 @@ namespace Bit.Droid.Autofill
Fields.Add(field);
AutofillIds.Add(field.AutofillId);
if(field.Hints != null)
if (field.Hints != null)
{
foreach(var hint in field.Hints)
foreach (var hint in field.Hints)
{
Hints.Add(hint);
if(field.Focused)
if (field.Focused)
{
FocusedHints.Add(hint);
}
if(!HintToFieldsMap.ContainsKey(hint))
if (!HintToFieldsMap.ContainsKey(hint))
{
HintToFieldsMap.Add(hint, new List<Field>());
}
@@ -157,10 +157,10 @@ namespace Bit.Droid.Autofill
public SavedItem GetSavedItem()
{
if(SaveType == SaveDataType.Password)
if (SaveType == SaveDataType.Password)
{
var passwordField = PasswordFields.FirstOrDefault(f => !string.IsNullOrWhiteSpace(f.TextValue));
if(passwordField == null)
if (passwordField == null)
{
return null;
}
@@ -178,7 +178,7 @@ namespace Bit.Droid.Autofill
savedItem.Login.Username = GetFieldValue(usernameField);
return savedItem;
}
else if(SaveType == SaveDataType.CreditCard)
else if (SaveType == SaveDataType.CreditCard)
{
var savedItem = new SavedItem
{
@@ -199,26 +199,26 @@ namespace Bit.Droid.Autofill
public AutofillId[] GetOptionalSaveIds()
{
if(SaveType == SaveDataType.Password)
if (SaveType == SaveDataType.Password)
{
return UsernameFields.Select(f => f.AutofillId).ToArray();
}
else if(SaveType == SaveDataType.CreditCard)
else if (SaveType == SaveDataType.CreditCard)
{
var fieldList = new List<Field>();
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardSecurityCode))
{
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardSecurityCode]);
}
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationYear))
{
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationYear]);
}
if(HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
if (HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardExpirationMonth))
{
fieldList.AddRange(HintToFieldsMap[View.AutofillHintCreditCardExpirationMonth]);
}
if(HintToFieldsMap.ContainsKey(View.AutofillHintName))
if (HintToFieldsMap.ContainsKey(View.AutofillHintName))
{
fieldList.AddRange(HintToFieldsMap[View.AutofillHintName]);
}
@@ -229,11 +229,11 @@ namespace Bit.Droid.Autofill
public AutofillId[] GetRequiredSaveFields()
{
if(SaveType == SaveDataType.Password)
if (SaveType == SaveDataType.Password)
{
return PasswordFields.Select(f => f.AutofillId).ToArray();
}
else if(SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
else if (SaveType == SaveDataType.CreditCard && HintToFieldsMap.ContainsKey(View.AutofillHintCreditCardNumber))
{
return HintToFieldsMap[View.AutofillHintCreditCardNumber].Select(f => f.AutofillId).ToArray();
}
@@ -247,12 +247,12 @@ namespace Bit.Droid.Autofill
private string GetFieldValue(string hint, bool monthValue = false)
{
if(HintToFieldsMap.ContainsKey(hint))
if (HintToFieldsMap.ContainsKey(hint))
{
foreach(var field in HintToFieldsMap[hint])
foreach (var field in HintToFieldsMap[hint])
{
var val = GetFieldValue(field, monthValue);
if(!string.IsNullOrWhiteSpace(val))
if (!string.IsNullOrWhiteSpace(val))
{
return val;
}
@@ -263,30 +263,30 @@ namespace Bit.Droid.Autofill
private string GetFieldValue(Field field, bool monthValue = false)
{
if(field == null)
if (field == null)
{
return null;
}
if(!string.IsNullOrWhiteSpace(field.TextValue))
if (!string.IsNullOrWhiteSpace(field.TextValue))
{
if(field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
if (field.AutofillType == AutofillType.List && field.ListValue.HasValue && monthValue)
{
if(field.AutofillOptions.Count == 13)
if (field.AutofillOptions.Count == 13)
{
return field.ListValue.ToString();
}
else if(field.AutofillOptions.Count == 12)
else if (field.AutofillOptions.Count == 12)
{
return (field.ListValue + 1).ToString();
}
}
return field.TextValue;
}
else if(field.DateValue.HasValue)
else if (field.DateValue.HasValue)
{
return field.DateValue.Value.ToString();
}
else if(field.ToggleValue.HasValue)
else if (field.ToggleValue.HasValue)
{
return field.ToggleValue.Value.ToString();
}
@@ -300,20 +300,20 @@ namespace Bit.Droid.Autofill
f.InputType.HasFlag(InputTypes.TextVariationWebPassword);
// For whatever reason, multi-line input types are coming through with TextVariationPassword flags
if(inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
if (inputTypePassword && f.InputType.HasFlag(InputTypes.TextVariationPassword) &&
f.InputType.HasFlag(InputTypes.TextFlagMultiLine))
{
inputTypePassword = false;
}
if(!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
if (!inputTypePassword && f.HtmlInfo != null && f.HtmlInfo.Tag == "input" &&
(f.HtmlInfo.Attributes?.Any() ?? false))
{
foreach(var a in f.HtmlInfo.Attributes)
foreach (var a in f.HtmlInfo.Attributes)
{
var key = a.First as Java.Lang.String;
var val = a.Second as Java.Lang.String;
if(key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
if (key != null && val != null && key.ToString() == "type" && val.ToString() == "password")
{
return true;
}
@@ -331,7 +331,7 @@ namespace Bit.Droid.Autofill
private bool ValueContainsAnyTerms(string value, HashSet<string> terms)
{
if(string.IsNullOrWhiteSpace(value))
if (string.IsNullOrWhiteSpace(value))
{
return false;
}

View File

@@ -27,7 +27,7 @@ namespace Bit.Droid.Autofill
Type = cipher.Type;
Subtitle = cipher.SubTitle;
switch(Type)
switch (Type)
{
case CipherType.Login:
Icon = Resource.Drawable.login;
@@ -62,32 +62,32 @@ namespace Bit.Droid.Autofill
public bool ApplyToFields(FieldCollection fieldCollection, Dataset.Builder datasetBuilder)
{
if(!fieldCollection?.Fields.Any() ?? true)
if (!fieldCollection?.Fields.Any() ?? true)
{
return false;
}
var setValues = false;
if(Type == CipherType.Login)
if (Type == CipherType.Login)
{
if(fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
if (fieldCollection.PasswordFields.Any() && !string.IsNullOrWhiteSpace(_password))
{
foreach(var f in fieldCollection.PasswordFields)
foreach (var f in fieldCollection.PasswordFields)
{
var val = ApplyValue(f, _password);
if(val != null)
if (val != null)
{
setValues = true;
datasetBuilder.SetValue(f.AutofillId, val);
}
}
}
if(fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
if (fieldCollection.UsernameFields.Any() && !string.IsNullOrWhiteSpace(Subtitle))
{
foreach(var f in fieldCollection.UsernameFields)
foreach (var f in fieldCollection.UsernameFields)
{
var val = ApplyValue(f, Subtitle);
if(val != null)
if (val != null)
{
setValues = true;
datasetBuilder.SetValue(f.AutofillId, val);
@@ -95,59 +95,59 @@ namespace Bit.Droid.Autofill
}
}
}
else if(Type == CipherType.Card)
else if (Type == CipherType.Card)
{
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardNumber,
_cardNumber))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardSecurityCode,
_cardCode))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection,
if (ApplyValue(datasetBuilder, fieldCollection,
Android.Views.View.AutofillHintCreditCardExpirationMonth, _cardExpMonth, true))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintCreditCardExpirationYear,
_cardExpYear))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, _cardName))
{
setValues = true;
}
}
else if(Type == CipherType.Identity)
else if (Type == CipherType.Identity)
{
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPhone, _idPhone))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintEmailAddress, _idEmail))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintUsername,
_idUsername))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalAddress,
_idAddress))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintPostalCode,
_idPostalCode))
{
setValues = true;
}
if(ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
if (ApplyValue(datasetBuilder, fieldCollection, Android.Views.View.AutofillHintName, Subtitle))
{
setValues = true;
}
@@ -159,12 +159,12 @@ namespace Bit.Droid.Autofill
string hint, string value, bool monthValue = false)
{
bool setValues = false;
if(fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
if (fieldCollection.HintToFieldsMap.ContainsKey(hint) && !string.IsNullOrWhiteSpace(value))
{
foreach(var f in fieldCollection.HintToFieldsMap[hint])
foreach (var f in fieldCollection.HintToFieldsMap[hint])
{
var val = ApplyValue(f, value, monthValue);
if(val != null)
if (val != null)
{
setValues = true;
builder.SetValue(f.AutofillId, val);
@@ -176,31 +176,31 @@ namespace Bit.Droid.Autofill
private static AutofillValue ApplyValue(Field field, string value, bool monthValue = false)
{
switch(field.AutofillType)
switch (field.AutofillType)
{
case AutofillType.Date:
if(long.TryParse(value, out long dateValue))
if (long.TryParse(value, out long dateValue))
{
return AutofillValue.ForDate(dateValue);
}
break;
case AutofillType.List:
if(field.AutofillOptions != null)
if (field.AutofillOptions != null)
{
if(monthValue && int.TryParse(value, out int monthIndex))
if (monthValue && int.TryParse(value, out int monthIndex))
{
if(field.AutofillOptions.Count == 13)
if (field.AutofillOptions.Count == 13)
{
return AutofillValue.ForList(monthIndex);
}
else if(field.AutofillOptions.Count >= monthIndex)
else if (field.AutofillOptions.Count >= monthIndex)
{
return AutofillValue.ForList(monthIndex - 1);
}
}
for(var i = 0; i < field.AutofillOptions.Count; i++)
for (var i = 0; i < field.AutofillOptions.Count; i++)
{
if(field.AutofillOptions[i].Equals(value))
if (field.AutofillOptions[i].Equals(value))
{
return AutofillValue.ForList(i);
}
@@ -210,7 +210,7 @@ namespace Bit.Droid.Autofill
case AutofillType.Text:
return AutofillValue.ForText(value);
case AutofillType.Toggle:
if(bool.TryParse(value, out bool toggleValue))
if (bool.TryParse(value, out bool toggleValue))
{
return AutofillValue.ForToggle(toggleValue);
}
@@ -221,4 +221,4 @@ namespace Bit.Droid.Autofill
return null;
}
}
}
}

View File

@@ -33,16 +33,16 @@ namespace Bit.Droid.Autofill
{
get
{
if(!string.IsNullOrWhiteSpace(_uri))
if (!string.IsNullOrWhiteSpace(_uri))
{
return _uri;
}
var websiteNull = string.IsNullOrWhiteSpace(Website);
if(websiteNull && string.IsNullOrWhiteSpace(PackageName))
if (websiteNull && string.IsNullOrWhiteSpace(PackageName))
{
_uri = null;
}
else if(!websiteNull)
else if (!websiteNull)
{
_uri = Website;
}
@@ -59,7 +59,7 @@ namespace Bit.Droid.Autofill
get => _packageName;
set
{
if(string.IsNullOrWhiteSpace(value))
if (string.IsNullOrWhiteSpace(value))
{
_packageName = _uri = null;
}
@@ -72,7 +72,7 @@ namespace Bit.Droid.Autofill
get => _website;
set
{
if(string.IsNullOrWhiteSpace(value))
if (string.IsNullOrWhiteSpace(value))
{
_website = _uri = null;
}
@@ -84,10 +84,10 @@ namespace Bit.Droid.Autofill
{
var fillable = !string.IsNullOrWhiteSpace(Uri) && !AutofillHelpers.BlacklistedUris.Contains(Uri) &&
FieldCollection != null && FieldCollection.Fillable;
if(fillable)
if (fillable)
{
var blacklistedUris = await storageService.GetAsync<List<string>>(Constants.AutofillBlacklistedUrisKey);
if(blacklistedUris != null && blacklistedUris.Count > 0)
if (blacklistedUris != null && blacklistedUris.Count > 0)
{
fillable = !new HashSet<string>(blacklistedUris).Contains(Uri);
}
@@ -98,20 +98,20 @@ namespace Bit.Droid.Autofill
public void Parse()
{
string titlePackageId = null;
for(var i = 0; i < _structure.WindowNodeCount; i++)
for (var i = 0; i < _structure.WindowNodeCount; i++)
{
var node = _structure.GetWindowNodeAt(i);
if(i == 0)
if (i == 0)
{
titlePackageId = GetTitlePackageId(node);
}
ParseNode(node.RootViewNode);
}
if(string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
if (string.IsNullOrWhiteSpace(PackageName) && string.IsNullOrWhiteSpace(Website))
{
PackageName = titlePackageId;
}
if(!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
if (!AutofillHelpers.TrustedBrowsers.Contains(PackageName) &&
!AutofillHelpers.CompatBrowsers.Contains(PackageName))
{
Website = null;
@@ -123,7 +123,7 @@ namespace Bit.Droid.Autofill
SetPackageAndDomain(node);
var hints = node.GetAutofillHints();
var isEditText = node.ClassName == "android.widget.EditText" || node?.HtmlInfo?.Tag == "input";
if(isEditText || (hints?.Length ?? 0) > 0)
if (isEditText || (hints?.Length ?? 0) > 0)
{
FieldCollection.Add(new Field(node));
}
@@ -132,7 +132,7 @@ namespace Bit.Droid.Autofill
FieldCollection.IgnoreAutofillIds.Add(node.AutofillId);
}
for(var i = 0; i < node.ChildCount; i++)
for (var i = 0; i < node.ChildCount; i++)
{
ParseNode(node.GetChildAt(i));
}
@@ -140,15 +140,15 @@ namespace Bit.Droid.Autofill
private void SetPackageAndDomain(ViewNode node)
{
if(string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
if (string.IsNullOrWhiteSpace(PackageName) && !string.IsNullOrWhiteSpace(node.IdPackage) &&
!_excludedPackageIds.Contains(node.IdPackage))
{
PackageName = node.IdPackage;
}
if(string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
if (string.IsNullOrWhiteSpace(Website) && !string.IsNullOrWhiteSpace(node.WebDomain))
{
var scheme = "http";
if((int)Build.VERSION.SdkInt >= 28)
if ((int)Build.VERSION.SdkInt >= 28)
{
scheme = node.WebScheme;
}
@@ -158,13 +158,13 @@ namespace Bit.Droid.Autofill
private string GetTitlePackageId(WindowNode node)
{
if(node != null && !string.IsNullOrWhiteSpace(node.Title))
if (node != null && !string.IsNullOrWhiteSpace(node.Title))
{
var slashPosition = node.Title.IndexOf('/');
if(slashPosition > -1)
if (slashPosition > -1)
{
var packageId = node.Title.Substring(0, slashPosition);
if(packageId.Contains("."))
if (packageId.Contains("."))
{
return packageId;
}

View File

@@ -0,0 +1,30 @@
using Android.Graphics.Drawables;
using Bit.App.Utilities;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
[assembly: ExportEffect(typeof(FabShadowEffect), "FabShadowEffect")]
namespace Bit.Droid.Effects
{
public class FabShadowEffect : PlatformEffect
{
protected override void OnAttached ()
{
if (Control is Android.Widget.Button button)
{
var gd = new GradientDrawable();
gd.SetColor(ThemeManager.GetResourceColor("FabColor").ToAndroid());
gd.SetCornerRadius(100);
button.SetBackground(gd);
button.Elevation = 6;
button.TranslationZ = 20;
}
}
protected override void OnDetached ()
{
}
}
}

View File

@@ -1,7 +1,4 @@
using Android.Support.Design.BottomNavigation;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Widget;
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -13,7 +10,7 @@ namespace Bit.Droid.Effects
{
protected override void OnAttached()
{
if(Element is Label label && Control is TextView textView)
if (Element is Label label && Control is TextView textView)
{
textView.SetTextSize(Android.Util.ComplexUnitType.Pt, (float)label.FontSize);
}
@@ -23,4 +20,4 @@ namespace Bit.Droid.Effects
{
}
}
}
}

View File

@@ -1,7 +1,4 @@
using Android.Support.Design.BottomNavigation;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Widget;
using Android.Widget;
using Bit.Droid.Effects;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -13,7 +10,7 @@ namespace Bit.Droid.Effects
{
protected override void OnAttached()
{
if(Control is TextView textView)
if (Control is TextView textView)
{
textView.SetTextIsSelectable(true);
}
@@ -23,4 +20,4 @@ namespace Bit.Droid.Effects
{
}
}
}
}

View File

@@ -1,7 +1,6 @@
using Android.Support.Design.BottomNavigation;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Views;
using Bit.Droid.Effects;
using Google.Android.Material.BottomNavigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -13,11 +12,11 @@ namespace Bit.Droid.Effects
{
protected override void OnAttached()
{
if(!(Container.GetChildAt(0) is ViewGroup layout))
if (!(Container.GetChildAt(0) is ViewGroup layout))
{
return;
}
if(!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
if (!(layout.GetChildAt(1) is BottomNavigationView bottomNavigationView))
{
return;
}
@@ -28,4 +27,4 @@ namespace Bit.Droid.Effects
{
}
}
}
}

View File

@@ -17,14 +17,15 @@ using Bit.Core.Enums;
using Android.Nfc;
using Bit.App.Utilities;
using System.Threading.Tasks;
using Android.Support.V4.Content;
using AndroidX.Core.Content;
using ZXing.Net.Mobile.Android;
namespace Bit.Droid
{
[Activity(
Label = "Bitwarden",
Icon = "@mipmap/ic_launcher",
Theme = "@style/LightTheme.Splash",
Theme = "@style/LaunchTheme",
MainLauncher = true,
ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
[Register("com.x8bit.bitwarden.MainActivity")]
@@ -37,7 +38,7 @@ namespace Bit.Droid
private IAppIdService _appIdService;
private IStorageService _storageService;
private IEventService _eventService;
private PendingIntent _lockAlarmPendingIntent;
private PendingIntent _vaultTimeoutAlarmPendingIntent;
private PendingIntent _clearClipboardPendingIntent;
private PendingIntent _eventUploadPendingIntent;
private AppOptions _appOptions;
@@ -51,7 +52,7 @@ namespace Bit.Droid
_eventUploadPendingIntent = PendingIntent.GetBroadcast(this, 0, eventUploadIntent,
PendingIntentFlags.UpdateCurrent);
var alarmIntent = new Intent(this, typeof(LockAlarmReceiver));
_lockAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
_vaultTimeoutAlarmPendingIntent = PendingIntent.GetBroadcast(this, 0, alarmIntent,
PendingIntentFlags.UpdateCurrent);
var clearClipboardIntent = new Intent(this, typeof(ClearClipboardAlarmReceiver));
_clearClipboardPendingIntent = PendingIntent.GetBroadcast(this, 0, clearClipboardIntent,
@@ -73,14 +74,14 @@ namespace Bit.Droid
UpdateTheme(ThemeManager.GetTheme(true));
base.OnCreate(savedInstanceState);
if(!CoreHelpers.InDebugMode())
if (!CoreHelpers.InDebugMode())
{
Window.AddFlags(Android.Views.WindowManagerFlags.Secure);
}
#if !FDROID
var hockeyAppListener = new HockeyAppCrashManagerListener(_appIdService, _userService);
var hockeyAppTask = hockeyAppListener.InitAsync(this);
var appCenterHelper = new AppCenterHelper(_appIdService, _userService);
var appCenterTask = appCenterHelper.InitAsync();
#endif
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
@@ -90,44 +91,44 @@ namespace Bit.Droid
_broadcasterService.Subscribe(_activityKey, (message) =>
{
if(message.Command == "scheduleLockTimer")
if (message.Command == "scheduleVaultTimeoutTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
var lockOptionMinutes = (int)message.Data;
var lockOptionMs = lockOptionMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + lockOptionMs + 10;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _lockAlarmPendingIntent);
var vaultTimeoutMinutes = (int)message.Data;
var vaultTimeoutMs = vaultTimeoutMinutes * 60000;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + vaultTimeoutMs + 10;
alarmManager.Set(AlarmType.RtcWakeup, triggerMs, _vaultTimeoutAlarmPendingIntent);
}
else if(message.Command == "cancelLockTimer")
else if (message.Command == "cancelVaultTimeoutTimer")
{
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Cancel(_lockAlarmPendingIntent);
alarmManager.Cancel(_vaultTimeoutAlarmPendingIntent);
}
else if(message.Command == "startEventTimer")
else if (message.Command == "startEventTimer")
{
StartEventAlarm();
}
else if(message.Command == "stopEventTimer")
else if (message.Command == "stopEventTimer")
{
var task = StopEventAlarmAsync();
}
else if(message.Command == "finishMainActivity")
else if (message.Command == "finishMainActivity")
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(() => Finish());
}
else if(message.Command == "listenYubiKeyOTP")
else if (message.Command == "listenYubiKeyOTP")
{
ListenYubiKey((bool)message.Data);
}
else if(message.Command == "updatedTheme")
else if (message.Command == "updatedTheme")
{
RestartApp();
}
else if(message.Command == "exit")
else if (message.Command == "exit")
{
ExitApp();
}
else if(message.Command == "copiedToClipboard")
else if (message.Command == "copiedToClipboard")
{
var task = ClearClipboardAlarmAsync(message.Data as Tuple<string, int?, bool>);
}
@@ -143,7 +144,8 @@ namespace Bit.Droid
protected override void OnResume()
{
base.OnResume();
if(_deviceActionService.SupportsNfc())
Xamarin.Essentials.Platform.OnResume();
if (_deviceActionService.SupportsNfc())
{
try
{
@@ -157,18 +159,18 @@ namespace Bit.Droid
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
if(intent.GetBooleanExtra("generatorTile", false))
if (intent.GetBooleanExtra("generatorTile", false))
{
_messagingService.Send("popAllAndGoToTabGenerator");
if(_appOptions != null)
if (_appOptions != null)
{
_appOptions.GeneratorTile = true;
}
}
if(intent.GetBooleanExtra("myVaultTile", false))
if (intent.GetBooleanExtra("myVaultTile", false))
{
_messagingService.Send("popAllAndGoToTabMyVault");
if(_appOptions != null)
if (_appOptions != null)
{
_appOptions.MyVaultTile = true;
}
@@ -182,9 +184,9 @@ namespace Bit.Droid
public async override void OnRequestPermissionsResult(int requestCode, string[] permissions,
[GeneratedEnum] Permission[] grantResults)
{
if(requestCode == Constants.SelectFilePermissionRequestCode)
if (requestCode == Constants.SelectFilePermissionRequestCode)
{
if(grantResults.Any(r => r != Permission.Granted))
if (grantResults.Any(r => r != Permission.Granted))
{
_messagingService.Send("selectFileCameraPermissionDenied");
}
@@ -193,20 +195,19 @@ namespace Bit.Droid
else
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
ZXing.Net.Mobile.Forms.Android.PermissionsHandler.OnRequestPermissionsResult(
requestCode, permissions, grantResults);
PermissionsHandler.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
if(resultCode == Result.Ok &&
if (resultCode == Result.Ok &&
(requestCode == Constants.SelectFileRequestCode || requestCode == Constants.SaveFileRequestCode))
{
Android.Net.Uri uri = null;
string fileName = null;
if(data != null && data.Data != null)
if (data != null && data.Data != null)
{
uri = data.Data;
fileName = AndroidHelpers.GetFileName(ApplicationContext, uri);
@@ -219,12 +220,12 @@ namespace Bit.Droid
fileName = $"photo_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.jpg";
}
if(uri == null)
if (uri == null)
{
return;
}
if(requestCode == Constants.SaveFileRequestCode)
if (requestCode == Constants.SaveFileRequestCode)
{
_messagingService.Send("selectSaveFileResult",
new Tuple<string, string>(uri.ToString(), fileName));
@@ -233,15 +234,15 @@ namespace Bit.Droid
try
{
using(var stream = ContentResolver.OpenInputStream(uri))
using(var memoryStream = new MemoryStream())
using (var stream = ContentResolver.OpenInputStream(uri))
using (var memoryStream = new MemoryStream())
{
stream.CopyTo(memoryStream);
_messagingService.Send("selectFileResult",
new Tuple<byte[], string>(memoryStream.ToArray(), fileName ?? "unknown_file_name"));
}
}
catch(Java.IO.FileNotFoundException)
catch (Java.IO.FileNotFoundException)
{
return;
}
@@ -256,12 +257,12 @@ namespace Bit.Droid
private void ListenYubiKey(bool listen)
{
if(!_deviceActionService.SupportsNfc())
if (!_deviceActionService.SupportsNfc())
{
return;
}
var adapter = NfcAdapter.GetDefaultAdapter(this);
if(listen)
if (listen)
{
var intent = new Intent(this, Class);
intent.AddFlags(ActivityFlags.SingleTop);
@@ -298,11 +299,11 @@ namespace Bit.Droid
FromAutofillFramework = Intent.GetBooleanExtra("autofillFramework", false)
};
var fillType = Intent.GetIntExtra("autofillFrameworkFillType", 0);
if(fillType > 0)
if (fillType > 0)
{
options.FillType = (CipherType)fillType;
}
if(Intent.GetBooleanExtra("autofillFrameworkSave", false))
if (Intent.GetBooleanExtra("autofillFrameworkSave", false))
{
options.SaveType = (CipherType)Intent.GetIntExtra("autofillFrameworkType", 0);
options.SaveName = Intent.GetStringExtra("autofillFrameworkName");
@@ -319,12 +320,12 @@ namespace Bit.Droid
private void ParseYubiKey(string data)
{
if(data == null)
if (data == null)
{
return;
}
var otpMatch = _otpPattern.Matcher(data);
if(otpMatch.Matches())
if (otpMatch.Matches())
{
var otp = otpMatch.Group(1);
_messagingService.Send("gotYubiKeyOTP", otp);
@@ -333,21 +334,32 @@ namespace Bit.Droid
private void UpdateTheme(string theme)
{
if(theme == "dark")
if (theme == "light")
{
SetTheme(Resource.Style.LightTheme);
}
else if (theme == "dark")
{
SetTheme(Resource.Style.DarkTheme);
}
else if(theme == "black")
else if (theme == "black")
{
SetTheme(Resource.Style.BlackTheme);
}
else if(theme == "nord")
else if (theme == "nord")
{
SetTheme(Resource.Style.NordTheme);
}
else
{
SetTheme(Resource.Style.LightTheme);
if (_deviceActionService.UsingDarkTheme())
{
SetTheme(Resource.Style.DarkTheme);
}
else
{
SetTheme(Resource.Style.LightTheme);
}
}
}
@@ -369,24 +381,23 @@ namespace Bit.Droid
private async Task ClearClipboardAlarmAsync(Tuple<string, int?, bool> data)
{
if(data.Item3)
if (data.Item3)
{
return;
}
var clearMs = data.Item2;
if(clearMs == null)
if (clearMs == null)
{
var clearSeconds = await _storageService.GetAsync<int?>(Constants.ClearClipboardKey);
if(clearSeconds != null)
if (clearSeconds != null)
{
clearMs = clearSeconds.Value * 1000;
}
}
if(clearMs == null)
if (clearMs == null)
{
return;
}
StaticStore.LastClipboardValue = data.Item1;
var triggerMs = Java.Lang.JavaSystem.CurrentTimeMillis() + clearMs.Value;
var alarmManager = GetSystemService(AlarmService) as AlarmManager;
alarmManager.Set(AlarmType.Rtc, triggerMs, _clearClipboardPendingIntent);

View File

@@ -37,14 +37,15 @@ namespace Bit.Droid
public MainApplication(IntPtr handle, JniHandleOwnership transer)
: base(handle, transer)
{
if(ServiceContainer.RegisteredServices.Count == 0)
if (ServiceContainer.RegisteredServices.Count == 0)
{
RegisterLocalServices();
var deviceActionService = ServiceContainer.Resolve<IDeviceActionService>("deviceActionService");
ServiceContainer.Init(deviceActionService.DeviceUserAgent);
ServiceContainer.Init(deviceActionService.DeviceUserAgent, Constants.ClearCiphersCacheKey,
Constants.AndroidAllClearCipherCacheKeys);
}
#if !FDROID
if(Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
if (Build.VERSION.SdkInt <= BuildVersionCodes.Kitkat)
{
ProviderInstaller.InstallIfNeededAsync(ApplicationContext, this);
}
@@ -70,7 +71,6 @@ namespace Bit.Droid
{
ServiceContainer.Register<ILogService>("logService", new AndroidLogService());
Refractored.FabControl.Droid.FloatingActionButtonViewRenderer.Init();
// Note: This might cause a race condition. Investigate more.
Task.Run(() =>
{
@@ -83,7 +83,6 @@ namespace Bit.Droid
ZXing.Net.Mobile.Forms.Android.Platform.Init();
});
CrossFingerprint.SetCurrentActivityResolver(() => CrossCurrentActivity.Current.Activity);
CrossFingerprint.SetDialogFragmentType<CustomFingerprintDialogFragment>();
var preferencesStorage = new PreferencesStorageService(null);
var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
@@ -100,6 +99,7 @@ namespace Bit.Droid
broadcasterService, () => ServiceContainer.Resolve<IEventService>("eventService"));
var platformUtilsService = new MobilePlatformUtilsService(deviceActionService, messagingService,
broadcasterService);
var biometricService = new BiometricService();
ServiceContainer.Register<IBroadcasterService>("broadcasterService", broadcasterService);
ServiceContainer.Register<IMessagingService>("messagingService", messagingService);
@@ -110,6 +110,7 @@ namespace Bit.Droid
ServiceContainer.Register<IStorageService>("secureStorageService", secureStorageService);
ServiceContainer.Register<IDeviceActionService>("deviceActionService", deviceActionService);
ServiceContainer.Register<IPlatformUtilsService>("platformUtilsService", platformUtilsService);
ServiceContainer.Register<IBiometricService>("biometricService", biometricService);
// Push
#if FDROID

View File

@@ -3,10 +3,11 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:versionCode="1"
android:versionName="2.3.0"
android:versionName="2.7.0"
android:installLocation="internalOnly"
package="com.x8bit.bitwarden">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="29" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.NFC" />
@@ -23,14 +24,14 @@
<application
android:label="Bitwarden"
android:theme="@style/LightTheme.Splash"
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="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="com.x8bit.bitwarden.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
@@ -55,5 +56,12 @@
<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" />
</application>
</manifest>

View File

@@ -16,12 +16,12 @@ namespace Bit.Droid.Push
{
public async override void OnMessageReceived(RemoteMessage message)
{
if(message?.Data == null)
if (message?.Data == null)
{
return;
}
var data = message.Data.ContainsKey("data") ? message.Data["data"] : null;
if(data == null)
if (data == null)
{
return;
}
@@ -32,7 +32,7 @@ namespace Bit.Droid.Push
"pushNotificationListenerService");
await listener.OnMessageAsync(obj, Device.Android);
}
catch(JsonReaderException ex)
catch (JsonReaderException ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}

View File

@@ -1,8 +1,4 @@
using Android.Content;
using Bit.Core;
using Bit.Core.Abstractions;
using Bit.Core.Utilities;
using Bit.Droid.Utilities;
namespace Bit.Droid.Receivers
{
@@ -12,11 +8,7 @@ namespace Bit.Droid.Receivers
public override void OnReceive(Context context, Intent intent)
{
var clipboardManager = context.GetSystemService(Context.ClipboardService) as ClipboardManager;
if(StaticStore.LastClipboardValue != null && StaticStore.LastClipboardValue == clipboardManager.Text)
{
clipboardManager.Text = string.Empty;
}
StaticStore.LastClipboardValue = null;
clipboardManager.PrimaryClip = ClipData.NewPlainText("bitwarden", string.Empty);
}
}
}

View File

@@ -9,8 +9,8 @@ namespace Bit.Droid.Receivers
{
public async override void OnReceive(Context context, Intent intent)
{
var lockService = ServiceContainer.Resolve<ILockService>("lockService");
await lockService.CheckLockAsync();
var vaultTimeoutService = ServiceContainer.Resolve<IVaultTimeoutService>("vaultTimeoutService");
await vaultTimeoutService.CheckVaultTimeoutAsync();
}
}
}

View File

@@ -14,7 +14,7 @@ namespace Bit.Droid.Receivers
{
public async override void OnReceive(Context context, Intent intent)
{
if(intent.Action == Intent.ActionApplicationRestrictionsChanged)
if (intent.Action == Intent.ActionApplicationRestrictionsChanged)
{
await AndroidHelpers.SetPreconfiguredRestrictionSettingsAsync(context);
}

View File

@@ -7,6 +7,7 @@ using Android.Views;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.App.Controls;
using Bit.App.Utilities;
using Bit.Droid.Renderers;
using FFImageLoading;
using FFImageLoading.Views;
@@ -26,40 +27,40 @@ namespace Bit.Droid.Renderers
private static Android.Graphics.Color _textColor;
private static Android.Graphics.Color _mutedColor;
private static Android.Graphics.Color _disabledIconColor;
private static bool _usingLightTheme;
private AndroidCipherCell _cell;
protected override Android.Views.View GetCellCore(Cell item, Android.Views.View convertView,
ViewGroup parent, Context context)
{
if(_faTypeface == null)
// TODO expand beyond light/dark detection once we support custom theme switching without app restart
var themeChanged = _usingLightTheme != ThemeManager.UsingLightTheme;
if (_faTypeface == null)
{
_faTypeface = Typeface.CreateFromAsset(context.Assets, "FontAwesome.ttf");
}
if(_miTypeface == null)
if (_miTypeface == null)
{
_miTypeface = Typeface.CreateFromAsset(context.Assets, "MaterialIcons_Regular.ttf");
}
if(_textColor == default(Android.Graphics.Color))
if (_textColor == default(Android.Graphics.Color) || themeChanged)
{
_textColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["TextColor"])
.ToAndroid();
_textColor = ThemeManager.GetResourceColor("TextColor").ToAndroid();
}
if(_mutedColor == default(Android.Graphics.Color))
if (_mutedColor == default(Android.Graphics.Color) || themeChanged)
{
_mutedColor = ((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["MutedColor"])
.ToAndroid();
_mutedColor = ThemeManager.GetResourceColor("MutedColor").ToAndroid();
}
if(_disabledIconColor == default(Android.Graphics.Color))
if (_disabledIconColor == default(Android.Graphics.Color) || themeChanged)
{
_disabledIconColor =
((Xamarin.Forms.Color)Xamarin.Forms.Application.Current.Resources["DisabledIconColor"])
.ToAndroid();
_disabledIconColor = ThemeManager.GetResourceColor("DisabledIconColor").ToAndroid();
}
_usingLightTheme = ThemeManager.UsingLightTheme;
var cipherCell = item as CipherViewCell;
_cell = convertView as AndroidCipherCell;
if(_cell == null)
if (_cell == null)
{
_cell = new AndroidCipherCell(context, cipherCell, _faTypeface, _miTypeface);
}
@@ -77,11 +78,11 @@ namespace Bit.Droid.Renderers
{
var cipherCell = sender as CipherViewCell;
_cell.CipherViewCell = cipherCell;
if(e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
if (e.PropertyName == CipherViewCell.CipherProperty.PropertyName)
{
_cell.UpdateCell(cipherCell);
}
else if(e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
else if (e.PropertyName == CipherViewCell.WebsiteIconsEnabledProperty.PropertyName)
{
_cell.UpdateIconImage(cipherCell);
}
@@ -145,7 +146,7 @@ namespace Bit.Droid.Renderers
var cipher = cipherCell.Cipher;
Name.Text = cipher.Name;
if(!string.IsNullOrWhiteSpace(cipher.SubTitle))
if (!string.IsNullOrWhiteSpace(cipher.SubTitle))
{
SubTitle.Text = cipher.SubTitle;
SubTitle.Visibility = ViewStates.Visible;
@@ -160,7 +161,7 @@ namespace Bit.Droid.Renderers
public void UpdateIconImage(CipherViewCell cipherCell)
{
if(_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
if (_currentTask != null && !_currentTask.IsCancelled && !_currentTask.IsCompleted)
{
_currentTask.Cancel();
}
@@ -168,7 +169,7 @@ namespace Bit.Droid.Renderers
var cipher = cipherCell.Cipher;
var iconImage = cipherCell.GetIconImage(cipher);
if(iconImage.Item2 != null)
if (iconImage.Item2 != null)
{
IconImage.SetImageResource(Resource.Drawable.login);
IconImage.Visibility = ViewStates.Visible;
@@ -197,7 +198,7 @@ namespace Bit.Droid.Renderers
private void MoreButton_Click(object sender, EventArgs e)
{
if(CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
if (CipherViewCell.ButtonCommand?.CanExecute(CipherViewCell.Cipher) ?? false)
{
CipherViewCell.ButtonCommand.Execute(CipherViewCell.Cipher);
}
@@ -205,7 +206,7 @@ namespace Bit.Droid.Renderers
protected override void Dispose(bool disposing)
{
if(disposing)
if (disposing)
{
MoreButton.Click -= MoreButton_Click;
}

View File

@@ -12,11 +12,20 @@ namespace Bit.Droid.Renderers
public CustomEditorRenderer(Context context)
: base(context)
{ }
// Workaround for issue described here:
// https://github.com/xamarin/Xamarin.Forms/issues/8291#issuecomment-617456651
protected override void OnAttachedToWindow()
{
base.OnAttachedToWindow();
EditText.Enabled = false;
EditText.Enabled = true;
}
protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
{
base.OnElementChanged(e);
if(Control != null && e.NewElement != null)
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);

View File

@@ -1,5 +1,9 @@
using Android.Content;
using System.ComponentModel;
using Android.Content;
using Android.Graphics;
using Android.Text;
using Android.Views.InputMethods;
using Android.Widget;
using Bit.Droid.Renderers;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
@@ -16,7 +20,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
{
base.OnElementChanged(e);
if(Control != null && e.NewElement != null)
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);
@@ -24,5 +28,46 @@ namespace Bit.Droid.Renderers
(ImeAction)ImeFlags.NoExtractUi;
}
}
// Workaround for failure to disable text prediction on non-password fields
// see https://github.com/xamarin/Xamarin.Forms/issues/10857
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
// Check if changed property is "IsPassword", otherwise ignore
if (e.PropertyName == Entry.IsPasswordProperty.PropertyName)
{
// Check if field type is text, otherwise ignore (numeric passwords, etc.)
EditText.InputType = Element.Keyboard.ToInputType();
bool isText = (EditText.InputType & InputTypes.ClassText) == InputTypes.ClassText,
isNumber = (EditText.InputType & InputTypes.ClassNumber) == InputTypes.ClassNumber;
if (isText || isNumber)
{
if (Element.IsPassword)
{
// Element is a password field, set inputType to TextVariationPassword which disables
// predictive text by default
EditText.InputType = EditText.InputType |
(isText ? InputTypes.TextVariationPassword : InputTypes.NumberVariationPassword);
}
else
{
// Element is not a password field, set inputType to TextVariationVisiblePassword to
// disable predictive text while still displaying the content.
EditText.InputType = EditText.InputType |
(isText ? InputTypes.TextVariationVisiblePassword : InputTypes.NumberVariationNormal);
}
// The workaround above forces a reset of the style properties, so we need to re-apply the font.
// see https://xamarin.github.io/bugzilla-archives/33/33666/bug.html
var typeface = Typeface.CreateFromAsset(Context.Assets, "RobotoMono_Regular.ttf");
if (Control is TextView label)
{
label.Typeface = typeface;
}
}
}
}
}
}

View File

@@ -15,7 +15,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Picker> e)
{
base.OnElementChanged(e);
if(Control != null && e.NewElement != null)
if (Control != null && e.NewElement != null)
{
Control.SetPadding(Control.PaddingLeft, Control.PaddingTop - 10, Control.PaddingRight,
Control.PaddingBottom + 20);

View File

@@ -16,7 +16,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<SearchBar> e)
{
base.OnElementChanged(e);
if(Control != null && e.NewElement != null)
if (Control != null && e.NewElement != null)
{
try
{

View File

@@ -0,0 +1,60 @@
using Android.Content;
using Android.Views;
using Bit.Droid.Renderers;
using Google.Android.Material.BottomNavigation;
using Xamarin.Forms;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms.Platform.Android.AppCompat;
[assembly: ExportRenderer(typeof(TabbedPage), typeof(CustomTabbedRenderer))]
namespace Bit.Droid.Renderers
{
public class CustomTabbedRenderer : TabbedPageRenderer, BottomNavigationView.IOnNavigationItemReselectedListener
{
private TabbedPage _page;
public CustomTabbedRenderer(Context context) : base(context) { }
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
if (e.NewElement != null)
{
_page = e.NewElement;
GetBottomNavigationView()?.SetOnNavigationItemReselectedListener(this);
}
else
{
_page = e.OldElement;
}
}
private BottomNavigationView GetBottomNavigationView()
{
for (var i = 0; i < ViewGroup.ChildCount; i++)
{
var childView = ViewGroup.GetChildAt(i);
if (childView is ViewGroup viewGroup)
{
for (var j = 0; j < viewGroup.ChildCount; j++)
{
var childRelativeLayoutView = viewGroup.GetChildAt(j);
if (childRelativeLayoutView is BottomNavigationView bottomNavigationView)
{
return bottomNavigationView;
}
}
}
}
return null;
}
async void BottomNavigationView.IOnNavigationItemReselectedListener.OnNavigationItemReselected(IMenuItem item)
{
if (_page?.CurrentPage?.Navigation != null && _page.CurrentPage.Navigation.NavigationStack.Count > 0)
{
await _page.CurrentPage.Navigation.PopToRootAsync();
}
}
}
}

View File

@@ -17,7 +17,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<ListView> e)
{
base.OnElementChanged(e);
if(Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
if (Control != null && e.NewElement != null && e.NewElement is ExtendedListView listView)
{
// Pad for FAB
Control.SetPadding(0, 0, 0, 170);

View File

@@ -1,6 +1,6 @@
using Android.Content;
using Android.Graphics.Drawables;
using Android.Support.V4.Content.Res;
using AndroidX.Core.Content.Resources;
using Bit.App.Controls;
using Bit.Droid.Renderers;
using Xamarin.Forms;
@@ -18,12 +18,12 @@ namespace Bit.Droid.Renderers
protected override void OnElementChanged(ElementChangedEventArgs<Slider> e)
{
base.OnElementChanged(e);
if(Control != null && Element is ExtendedSlider view)
if (Control != null && Element is ExtendedSlider view)
{
var t = ResourcesCompat.GetDrawable(Resources, Resource.Drawable.slider_thumb, null);
if(t is GradientDrawable thumb)
if (t is GradientDrawable thumb)
{
if(view.ThumbColor == Color.Default)
if (view.ThumbColor == Color.Default)
{
thumb.SetColor(Color.White.ToAndroid());
}

View File

@@ -28,20 +28,20 @@ namespace Bit.Droid.Renderers
{
base.OnElementChanged(e);
if(Control == null)
if (Control == null)
{
var webView = new AWebkit.WebView(_context);
webView.Settings.JavaScriptEnabled = true;
webView.SetWebViewClient(new JSWebViewClient(string.Format("javascript: {0}", JSFunction)));
SetNativeControl(webView);
}
if(e.OldElement != null)
if (e.OldElement != null)
{
Control.RemoveJavascriptInterface("jsBridge");
var hybridWebView = e.OldElement as HybridWebView;
hybridWebView.Cleanup();
}
if(e.NewElement != null)
if (e.NewElement != null)
{
Control.AddJavascriptInterface(new JSBridge(this), "jsBridge");
Control.LoadUrl(Element.Uri);
@@ -51,7 +51,7 @@ namespace Bit.Droid.Renderers
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if(e.PropertyName == HybridWebView.UriProperty.PropertyName)
if (e.PropertyName == HybridWebView.UriProperty.PropertyName)
{
Control.LoadUrl(Element.Uri);
}
@@ -70,7 +70,7 @@ namespace Bit.Droid.Renderers
[Export("invokeAction")]
public void InvokeAction(string data)
{
if(_hybridWebViewRenderer != null &&
if (_hybridWebViewRenderer != null &&
_hybridWebViewRenderer.TryGetTarget(out HybridWebViewRenderer hybridRenderer))
{
hybridRenderer.Element.InvokeAction(data);

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 487 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 904 B

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 943 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 825 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 635 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 B

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TODO: When API 23 becomes our new minimum, replace 'splash_screen.xml' in 'drawable' with this version, and delete
all versions of 'logo_legacy.png' and 'logo_white_legacy.png'. The bitmaps only exist due to a bug in API <23 where
vector drawables used in layer-lists (our splash screen) stretch to fill the full height of the parent.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/lightgray"/>
</item>
<item
android:drawable="@drawable/logo"
android:tileMode="disabled"
android:gravity="center"/>
</layer-list>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
TODO: When API 23 becomes our new minimum, replace 'splash_screen_dark.xml' in 'drawable' with this version, and delete
all versions of 'logo_legacy.png' and 'logo_white_legacy.png'. The bitmaps only exist due to a bug in API <23 where
vector drawables used in layer-lists (our splash screen) stretch to fill the full height of the parent.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/darkgray"/>
</item>
<item
android:drawable="@drawable/logo_white"
android:tileMode="disabled"
android:gravity="center"/>
</layer-list>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 941 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 545 B

Some files were not shown because too many files have changed in this diff Show More